内存屏障

内存屏障(英语:Memory barrier),也称内存栅栏内存栅障屏障指令等,是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在内存屏障之前的指令和之后的指令不会由于系统优化等原因而导致乱序。

大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。

语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。

举例

当驱动程式执行下列动作时,如果处理器的写入指令 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,或者APIpthreadWindows 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()

参考文献

  1. ^ SFENCE—Store Fence. [2014-01-10]. (原始内容存档于2019-06-13). 
  2. ^ MFENCE—Memory Fence. [2014-01-10]. (原始内容存档于2019-09-05). 
  3. ^ MSDN:Synchronization and Multiprocessor Issues. [2016-09-05]. (原始内容存档于2017-07-04). 
  4. ^ Volatile Considered Harmful - Linux Kernel Documentation. [2014-01-10]. (原始内容存档于2013-11-02). 

外部链接