Chinaunix首页 | 论坛 | 博客
  • 博客访问: 336797
  • 博文数量: 79
  • 博客积分: 2466
  • 博客等级: 大尉
  • 技术积分: 880
  • 用 户 组: 普通用户
  • 注册时间: 2006-02-07 16:47
文章分类

全部博文(79)

文章存档

2014年(3)

2012年(7)

2011年(14)

2010年(2)

2009年(2)

2008年(2)

2007年(18)

2006年(31)

分类:

2009-12-30 17:07:31

为什么要对内存访问操作顺序进行重排?

 

简单的答案是:为了性能。CPU现在已经快得离谱,几个M的缓存也满足不了CPU的需要了。所以Cache经常被划分为几乎相互独立的几个bank这样多个bank就可以并行工作,以减小缓存和CPU之间的性能差距。Memory按照地址被划分成几块,对应到不同cache bank。例如所有标号为偶数的cache linebank 0处理,奇数标号的由bank 1处理。但是这种硬件层面上的并行也有坏处:内存操作可能不按指定的顺序完成。这可能会造成一些混乱。例如CPU0可能首先向0x12345000写入内容,这是一个奇数编号的cache line,假设它由bank0处理;然后再向0x12345100写入内容,这是一个偶数编号的cache line,假设它由bank1处理。如果bank0当时正忙于一些前面尚未完成的操作,而bank1是空闲的,这样CPU1就很可能先看到第二个写操作的结果。换句话说,CPU1“感知”到的这两个写操作的顺序,就和我们的预期不同。读操作也有可能类似地被打乱顺序。这种乱序可能引起教科书上的很多并行算法失败。

 

内存操作顺序重排和对称多处理器系统下的软件

 

一部分机器能够提供“顺序一致性”,即所有内存操作按照代码指定的顺序发生,而且系统中所有CPU所见的内存操作全局顺序完全一致。顺序一致的系统有很多优点,但往往性能不高。全局的顺序一致性极大地限制了硬件的并行处理能力,因此商用的CPU和系统不提供顺序一致性。

 

在这样的系统上,要区分一下三个顺序:

 

1. 程序顺序:某CPU上运行的程序代码中指定的内存操作顺序。

2. 执行顺序:某CPU执行内存访问指令的顺序。执行顺序可能和程序顺序不同,因为编译器和CPU都可能基于性能优化的需要,对内存操作进行顺序重排。

3. 感知顺序:某CPU“感知”到的、自身和其他CPU上的内存操作顺序。由于缓存、CPU间的连接和内存系统的某些优化措施,感知顺序可能和执行顺序不同。对同一内存操作指令序列来说,不同的CPU感知到的顺序可能是不同的。

 

比较流行的内存一致性模型包括:x86的进程一致性模型(某一CPU上的写操作,在所有CPU上感知到的顺序都相同);弱一致性模型(显式指定的两个内存屏障指令之间的所有内存操作,顺序可以任意重排)。谈到内存操作顺序重排在不同CPU上的效果时,总是既有好消息也有坏消息。坏消息是,每个CPU的内存操作顺序重排方式都有独特之处;好消息是,你总是可以认为下列条件成立:

1.    一个CPU对发生在其上的内存操作,感知顺序总是和程序顺序相同。即内存访问操作顺序重排带来的问题,仅当一个CPU在观察其他CPU的内存操作时才会出现。

2.    仅当某指令和另外一个存储指令访问的不是同一个地址,他们的顺序才可以被打乱。

3.    地址对齐的装载和存储指令都是原子操作。

4.    Linux内核的同步原语包含了所需的任何内存屏障指令,使用它们的时候就不必担心乱序问题。SpinlockssemaphoresRCU都无需再显式使用内存屏障;只有那些比较微妙的、不使用这些同步原语的代码才需要内存屏障。特别需要注意的是,多数原子操作,如atomic_inc()atomic_add(),并不包含内存屏障。

 

Linux如何解决内存操作乱序带来的问题?

 

Linux能运行在很多种不同的CPU上,遗憾的是,这些CPU所支持的内存一致性模型并不相同,Linux的内核是如何做到可移植的呢?

 

 Linux提供了一些精挑细选的内存屏障原语:

      smp_mb():一个既用于限制读操作也用于限制写操作的内存屏障。该屏障前的读写操作保证会在屏障后的任何读写操作之前完成。

      smp_rmb():内存读屏障。只用于限制内存读取操作。

      smp_wmb():内存写屏障。只用于限制内存写入操作。

      smp_read_barrier_depends():对其后续操作中所有依赖于其前导操作的部分,强制其顺序不变。这个原语在Alpha以外的所有平台上都是一个空操作。

 

smp_mb()smp_rmb()smp_wmb()原语还强制编译器放弃优化措施,如果该优化措施会对跨越屏障两边的内存操作顺序进行重排。smp_read_barrier_depends()Alpha CPU上也有同样的功能。

 

这些原语仅在SMP的内核上才生成相应代码,而且每个都有一个对应的单处理器版本: mb()rmb()wmb()read_barrier_depends()——这些单处理器版本在单处理器的内核中也生成内存屏障代码。绝大多数场合都应该用smp_开头的版本。单处理器的版本在写设备驱动的时候有用处,因为内存映射I/O的内存访问在单处理器的内核中也要保证顺序不变。没有这些内存屏障原语,编译器和CPU硬件都可能重排内存操作顺序,其结果,好的时候是让设备工作不正常;坏的就能让内核崩溃甚至损坏硬件。

阅读(978) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~