内存屏障

内存屏障英語:),也称内存栅栏内存栅障屏障指令等,是一类同步屏障指令,它使得 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,或者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. . [2014-01-10]. (原始内容存档于2019-06-13).
  2. . [2014-01-10]. (原始内容存档于2019-09-05).
  3. . [2016-09-05]. (原始内容存档于2017-07-04).
  4. . [2014-01-10]. (原始内容存档于2013-11-02).

外部链接

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.