Chinaunix首页 | 论坛 | 博客
  • 博客访问: 335245
  • 博文数量: 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-25 16:52:13

Memory barrier也称为membarmemory fencefence 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也有很大不同。正确使用内存屏障需要仔细研读相关硬件体系结构的手册。

 

高层的编程环境通常提供自己的同步原语,如javaPosix线程库或者Win32。它们都有互斥锁、信号灯等设施,可以用于对资源访问操作进行同步。这些高层的同步原语通常通过底层的内存屏障机制实现,他们保证提供预先定义好的内存可见性语义。这样的编程环境中无需直接使用内存屏障。

 

每一套api或编程环境,概念上都有它自己的高层的memory model,用来定义它自己的内存可见性语义。尽可能地了解你所使用的编程环境所定义的内存可见性语义,是一件很重要的事。

 

一个编程环境的memory model和硬件的memory model是不同抽象层次上的两件事。必须知道它们不是一回事,底层的硬件内存屏障,到高层的编程环境定义的内存可见性语义之间,往往没有一个简单的映射关系。所以一个特定硬件平台对某个编程环境/API的实现中使用的内存屏障,可能比相应的规范要求的更严格。如果程序利用了这些实现细节,而不是严格按照规范编程,那么很可能这个程序就是不可移植的。

 

内存屏障是从硬件的层次上控制执行顺序。编译器对程序的优化过程中,也可能重排指令的顺序。如果有些数据需要在多个并行的执行序列中共享,就需要特别指示编译器,在优化过程中不要重排指令顺序。如果这样的数据已经被编程环境/API定义的同步原语保护起来,就无需再做这种指示。

 

CC++中,关键字volatile就是用来使程序可以直接访问memory-mapped I/O。内存映射I/O通常都要求,内存读写操作发生的顺序,和他们在源代码中出现的顺序一致,并且不能被跳过。C/C++编译器不许对volatile的、用于memory-mapped I/O的内存操作进行顺序重排或者跳过。

 

CC++标准对多线程/多处理器的情况没有规定,所以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类似的语义。

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