分类:
2009-12-25 16:52:13
Memory barrier也称为membar或memory
fence、fence instruction。它是一种屏障和一类指令,用于指挥cpu或编译器,保证屏障指令前后的内存操作按照指定的顺序发生。
cpu的某些性能优化措施可能导致指令的乱序执行,这种乱序包括内存读写操作。内存操作指令的执行顺序变化,在单一的执行序列中通常不会影响执行结果;但在并行程序和设备驱动程序中,如果不仔细控制,就会引起无法预知的后果。顺序控制是硬件相关的,由相关硬件体系结构的memory
ordering model定义。有些体系结构提供多种屏障,以满足不同的顺序控制需求。
如果一段代码用于操作多个设备间的共享内存,其中通常都会有一些代码实现内存屏障。这样的代码包括:同步原语,多处理器系统上无需加锁的数据结构,以及和硬件打交道的设备驱动程序。
如果程序在单一的cpu上运行,硬件可以保证程序执行的顺序即使被打乱,最后的运行结果也是正常的——看上去就好像内存访问的顺序严格按照程序指定的顺序进行。这种情况下不需要内存屏障。但当多个设备共享内存时,(比如多处理器系统中的多个cpu,或者memory-mapped外设)内存的乱序访问就可能改变程序行为。例子:
Processor #1:
loop:
load the value in
location f, if it is 0 goto loop
print the value in
location x
Processor #2:
store the value 42
into location x
store the value 1
into location f
逻辑分析的结果是,print输出的结果永远是42。但如果processor
2的存储指令顺序被打乱,可能在x变成42之前f就变成了1,结果就是print输出0。对于绝大多数程序来说这样的结果是不能接受的。可以在Processor2的两个store指令之间插入一个内存屏障,以保证在f的值被改变之前,其它cpu都能读到x的新值42。
内存屏障是很低层的原语。它是给定的体系结构的memory model定义的一部分。和指令集一样,不同体系结构上的memory
model也有很大不同。正确使用内存屏障需要仔细研读相关硬件体系结构的手册。
高层的编程环境通常提供自己的同步原语,如java、Posix线程库或者Win32。它们都有互斥锁、信号灯等设施,可以用于对资源访问操作进行同步。这些高层的同步原语通常通过底层的内存屏障机制实现,他们保证提供预先定义好的内存可见性语义。这样的编程环境中无需直接使用内存屏障。
每一套api或编程环境,概念上都有它自己的高层的memory
model,用来定义它自己的内存可见性语义。尽可能地了解你所使用的编程环境所定义的内存可见性语义,是一件很重要的事。
一个编程环境的memory model和硬件的memory
model是不同抽象层次上的两件事。必须知道它们不是一回事,底层的硬件内存屏障,到高层的编程环境定义的内存可见性语义之间,往往没有一个简单的映射关系。所以一个特定硬件平台对某个编程环境/API的实现中使用的内存屏障,可能比相应的规范要求的更严格。如果程序利用了这些实现细节,而不是严格按照规范编程,那么很可能这个程序就是不可移植的。
内存屏障是从硬件的层次上控制执行顺序。编译器对程序的优化过程中,也可能重排指令的顺序。如果有些数据需要在多个并行的执行序列中共享,就需要特别指示编译器,在优化过程中不要重排指令顺序。如果这样的数据已经被编程环境/API定义的同步原语保护起来,就无需再做这种指示。
C和C++中,关键字volatile就是用来使程序可以直接访问memory-mapped
I/O。内存映射I/O通常都要求,内存读写操作发生的顺序,和他们在源代码中出现的顺序一致,并且不能被跳过。C/C++编译器不许对volatile的、用于memory-mapped
I/O的内存操作进行顺序重排或者跳过。
C和C++标准对多线程/多处理器的情况没有规定,所以volatile的有效性依赖于编译器和硬件。虽然volatile保证内存读写操作会按照它们在代码里出现的顺序执行,但编译器生成代码的时候仍然可能打乱volatile和非volatile的内存之间的读写顺序,从而使volatile的内存作为线程间的标志或互斥锁的作用有所降低。避免这种问题的方法是依赖于具体编译器的。这里特别提一下gcc。对带有volatile和"memory"标签的内嵌汇编代码,gcc不会重排它附近的读写操作。例如:asm
volatile ("" : : : "memory");。另外,对volatile内存的读写,在其他的cpu看来可能会按不同的顺序进行,因为影响的因素很多,包括缓存、缓存一致性协议、relaxed
memory ordering等。也就是说单独使用的volatile变量,如果作为线程间的标志或者互斥锁,可能根本就是无效的。
某些编程语言和编译器可能提供足够的设施,使得用户可以控制编译器以及硬件对读写操作的重排。Java 1.5中,关键字volatile保证可以避免编译器和硬件对读写操作的重排,这是java 5的新memory model定义的一部分。C++0x中将会出现一些特别的原子类型和原子操作,具有与Java 5 Memory Model中的volatile类似的语义。