内存屏障
内存屏障(英語:),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在memory barrier 之前的指令和memory barrier之后的指令不会由于系统优化等原因而导致乱序。
大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。
语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。
舉例
當驅動程式執行下列動作時,如果處理器的寫入指令 out-of-order,使得資料還沒有寫入記憶體,硬體模塊就被觸發開始動作,就會產生錯誤的行為。
寫資料到記憶體, 稍後硬體模塊會存取這一筆資料
// 此處需要內存屏障
觸發硬體模塊開始處理資料
底层体系结构相关的原语
大多数处理器提供了内存屏障指令:
- 完全内存屏障(full memory barrier)保障了早于屏障的内存读写操作的结果提交到内存之后,再执行晚于屏障的读写操作。
- 内存读屏障(read memory barrier)仅确保了内存读操作;
- 内存写屏障(write memory barrier)仅保证了内存写操作。
内存屏障是底层原语,是内存排序的一部分,在不同体系结构下变化很大而不适合推广。需要认真研读硬件的手册以确定内存屏障的办法。x86指令集中的内存屏障指令是:
lfence (asm), void _mm_lfence (void) 读操作屏障 sfence (asm), void _mm_sfence (void)[1] 写操作屏障 mfence (asm), void _mm_mfence (void)[2] 读写操作屏障
常见的x86/x64,通常使用lock指令前缀加上一个空操作来实现,注意当然不能真的是nop指令,但是可以用来实现空操作的指令其实是很多的,比如Linux中采用的
addl $0, 0 (%esp)
存储器也提供了另一套语义[3]的内存屏障指令:
- acquire semantics: 该操作结果可利用要早于代码中后续的所有操作的结果。
- release semantics: 该操作结果可利用要晚于代码中之前的所有操作的结果。
- fence semantics: acquire与release两种语义的共同有效。即该操作结果可利用要晚于代码中之前的所有操作的结果,且该操作结果可利用要早于代码中后续的所有操作的结果。
Intel Itanium处理器,具有内存屏障mf的指令,具有下述modifiers:
- acq (acquire)
- rel (release).
Windows API的内存屏障实现
下述同步函数使用适当的屏障来确保内存有序:
- 进出临界区(critical section)的函数
- 触发(signaled)同步对象的函数
- 等待函数(Wait function)
- 互锁函数(Interlocked function)
多线程编程与内存可见性
多线程程序通常使用高层程序设计语言中的同步原语,如Java与.NET Framework,或者API如pthread或Windows API。因此一般不需要明确使用内存屏障。
内存可见性问题,主要是高速缓存与内存的一致性问题。一个处理器上的线程修改了某数据,而在另一处理器上的线程可能仍然使用着该数据在专用cache中的老值,这就是可见性出了问题。解决办法是令该数据为volatile属性,或者读该数据之前执行内存屏障。
乱序执行与编译器重排序优化的比较
C与C++语言中,volatile关键字意图允许内存映射的I/O操作。这要求编译器对此的数据读写按照程序中的先后顺序执行,不能对volatile内存的读写重排序。因此关键字volatile并不保证是一个内存屏障。[4]
对于Visual Studio 2003,编译器保证对volatile的操作是有序的,但是不能保证处理器的乱序执行。因此,可以使用InterlockedCompareExchange或InterlockedExchange函数。
对于Visual Studio 2005及以后版本,编译器对volatile变量的读操作使用acquire semantics,对写操作使用release semantics。
编译器内存屏障
编译器会对生成的可执行代码做一定优化,造成乱序执行甚至省略(不执行)。gcc编译器在遇到内嵌汇编语句:
asm volatile("" ::: "memory");
将以此作为一条内存屏障,重排序内存操作。即此语句之前的各种编译优化将不会持续到此语句之后。也可用内建的__sync_synchronize
Microsoft Visual C++的编译器内存屏障为:
_ReadWriteBarrier() MemoryBarrier()
Intel C++编译器的内存屏障为:
__memory_barrier()
参考文献
外部链接
- Memory Barriers: a Hardware View for Software Hackers 页面存档备份,存于
- Microsoft Driver Development: Memory Barriers on Multiprocessor Architectures
- HP technical report HPL-2004-209: Threads Cannot be Implemented as a Library 页面存档备份,存于
- Linux kernel memory barrier issues on multiple types of CPUs 页面存档备份,存于
- Documentation on memory barriers in the Linux kernel
- Handling Memory Ordering in Multithreaded Applications with Oracle Solaris Studio 12 Update 2: Part 1, Compiler Barriers 页面存档备份,存于
- Handling Memory Ordering in Multithreaded Applications with Oracle Solaris Studio 12 Update 2: Part 2, Memory Barriers and Memory Fences 页面存档备份,存于
- User-space RCU: Memory-barrier menagerie 页面存档备份,存于