內存屏障
內存屏障(英語: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,或者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()
參考文獻
[編輯]- ^ SFENCE—Store Fence. [2014-01-10]. (原始內容存檔於2019-06-13).
- ^ MFENCE—Memory Fence. [2014-01-10]. (原始內容存檔於2019-09-05).
- ^ MSDN:Synchronization and Multiprocessor Issues. [2016-09-05]. (原始內容存檔於2017-07-04).
- ^ Volatile Considered Harmful - Linux Kernel Documentation. [2014-01-10]. (原始內容存檔於2013-11-02).
外部連結
[編輯]- 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 (頁面存檔備份,存於網際網路檔案館)