__asm__ __volatile__("": : :"memory")

__asm__ __volatile__("": : :"memory");內存屏障(memory barrier) 
#define set_mb(var, value) do { var = value; mb(); } while (0) 
#define mb() __asm__ __volatile__ ("" : : : "memory") 

1)set_mb(),mb(),barrier()函數追蹤到底,就是__asm__ __volatile__("":::"memory"),而這行代碼就是內存屏障。 
2)__asm__用於指示編譯器在此插入彙編語句 
3)__volatile__用於告訴編譯器,嚴禁將此處的彙編語句與其它的語句重組合優化。即:原原本本按原來的樣子處理這這裏的彙編。 
4) memory強制gcc編譯器假設RAM所有內存單元均被彙編指令修改,這樣cpu中的registers和cache中已緩存的內存單元中的數據將作 廢。cpu將不得不在需要的時候重新讀取內存中的數據。這就阻止了cpu又將registers,cache中的數據用於去優化指令,而避免去訪問內存。 
5)"":::表示這是個空指令。barrier()不用在此插入一條串行化彙編指令。在後文將討論什麼叫串行化指令。 
6)__asm__,__volatile__,memory在前面已經解釋在linux/include/asm-i386/system.h定義:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
7)lock前綴表示將後面這句彙編語句:"addl $0,0(%%esp)"作爲cpu的一個內存屏障。
8)addl $0,0(%%esp)表示將數值0加到esp寄存器中,而該寄存器指向棧頂的內存單元。加上一個0,esp寄存器的數值依然不變。即這是一條無用的彙編 指令。在此利用這條無價值的彙編指令來配合lock指令,在__asm__,__volatile__,memory的作用下,用作cpu的內存屏障。
9)set_task_state()帶有一個memory barrier,set_task_state()肯定是安全的,但 __set_task_state()可能會快些。


關於barrier()宏實際上也是優化屏障:
#define barrier() __asm__ __volatile__("": : :"memory")
CPU越過內存屏障後,將刷新自己對存儲器的緩衝狀態。這條語句實際上不生成任何代碼,但可使gcc在barrier()之後刷新寄存器對變量的分配。     
             例1:
                1        int a = 5, b = 6;
                2        barrier();
                3        a = b;
      
            在line 3,GCC不會用存放b的寄存器給a賦值,而是重新讀內存中的b值,賦值給a。
           
           
例2:
它在進程上下文中將一個元素插入一個單向鏈表:
new->next=i->next;
wmb();
i->next=new;
同時,如果不加鎖地遍歷這個單向鏈表。或者在遍歷鏈表時已經可以看到new,或者new還不在該鏈表中。兩個內存寫
事件的順序必須按照程序順序進行。否則可能new的next指針將指向一個無效地址,就很可能出現 OOPS!

不論是gcc編譯器的優化還是處理器本身採用的大量優化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues 等,都可能使得實際執行可能違反程序順序,因此,引入內存屏障來保證事件的執行次序嚴格按程序順序來執行。

使用內存屏障強加的嚴格的CPU內存事件次序,保證程序的執行看上去象是遵循順序一致性模型。在當前的實現 中,wmb() 實際上是一個空操作,這是因爲目前Intel的CPU系列都遵循“處理機一致性”,所有的寫操作是遵循程序順序的,不會越過前面的讀寫操作。但是,由於 Intel CPU系列可能會在將來採用更弱的內存一致性模型並且其他體系結構可能採用其他放鬆的一致性模型,仍然在內核裏必須適當地插入wmb()保證內存事件的正 確次序。



在linux/include/asm-i386/alternative.h定義如下:
#define alternative(oldinstr, newinstr, feature) \
asm volatile ("661:\n\t" oldinstr "\n662:\n" \
".section .altinstructions,\"a\"\n" \
" .align 4\n" \
" .long 661b\n" /* label */ \
" .long 663f\n" /* new instruction */ \
" .byte %c0\n" /* feature bit */ \
" .byte 662b-661b\n" /* sourcelen */ \
" .byte 664f-663f\n" /* replacementlen */ \
".previous\n" \
".section .altinstr_replacement,\"ax\"\n" \
"663:\n\t" newinstr "\n664:\n" /* replacement */ \
".previous" :: "i" (feature) : "memory") 

1.alternative()宏用於在不同的cpu上優化指令。oldinstr爲舊指令,newinstr爲新指令,feature爲cpu特徵位。
2.oldinstr的長度必須>=newinstr的長度。不夠將填充空操作符。

注:這是源代碼註釋,具體我也看不懂

在linux/include/asm-i386/system.h定義如下:
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)

#define read_barrier_depends() do { } while(0)

#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE,but make it already an possibility. */
-->OOO:Out of Order,亂序執行。
-->SSE:SSE是英特爾提出的即MMX之後新一代(當然是幾年前了)CPU指令集,最早應用在PIII系列CPU上。
本小段內核註釋意即:亂序存儲的cpu還沒有問世,故CONFIG_X86_OOSTORE也就仍未定義的,wmb()當爲後面空宏(在__volatile__作用下,阻止編譯器重排順序優化)。

#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb() __asm__ __volatile__ ("": : :"memory")
#endif

1.lock前綴表示將後面這句彙編語句:"addl $0,0(%%esp)"作爲cpu的一個內存屏障。addl $0,0(%%esp)表示將數值0加到esp寄存器中,而該寄存器指向棧頂的內存單元。加上一個0,esp寄存器的數值依然不變。即這是一條無用的彙編 指令。在此利用這條無價值的彙編指令來配合lock指令,用作cpu的內存屏障。

2.mfence保證系統在後面的memory訪問之前,先前的memory訪問都已經結束。這是mfence是X86cpu家族中的新指令。

3.新舊指令對比:
以前的源代碼:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
__asm__用於指示編譯器在此插入彙編語句
__volatile__用於告訴編譯器,嚴禁將此處的彙編語句與其它的語句重組合優化。即:原原本本按原來的樣子處理這這裏的彙編。

現在的源代碼:
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
兩者比較:
比起以前的源代碼來少了__asm__和__volatile__。增加了alternative()宏和mfence指令。


而SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon處理器中引入)提供了某些特殊類型內存操作的排序和串行化功能。sfence,lfence,mfence指令是在後繼的cpu中新出現的的指 令。

SFENCE,LFENCE,MFENCE指令提供了高效的方式來保證讀寫內存的排序,這種操作發生在產生弱排序數據的程序和讀取這個數據的程序之間。
SFENCE——串行化發生在SFENCE指令之前的寫操作但是不影響讀操作。
LFENCE——串行化發生在SFENCE指令之前的讀操作但是不影響寫操作。
MFENCE——串行化發生在MFENCE指令之前的讀寫操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更靈活有效的控制內存排序的方式。

sfence:在sfence指令前的寫操作當必須在sfence指令後的寫操作前完成。
lfence:在lfence指令前的讀操作當必須在lfence指令後的讀操作前完成。
mfence:在mfence指令前的讀寫操作當必須在mfence指令後的讀寫操作前完成。

其實這裏是用mfence新指令來替換老的指令串:__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")。
mfence的執行效果就等效於 __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")的執行效果。只不過,__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")是在以前的cpu平臺上所設計的,藉助於編譯器__asm__,__volatile__,lock這些指令來實現內存屏障。而在 Pentium 4和Intel Xeon 處理器中由於已經引入了mfence指令,無須再用這一套指令,直接調用這一條指令即ok。而alternative()宏就是用於這個優化指令的替換, 用新的指令來替換老的指令串。


這些函數在已編譯的指令流中插入硬件內存屏障;具體的插入方法是平臺相關的。rmb(讀內存屏障)保證了屏障之前的讀操作一定會在後來的讀操作執行之前完成。wmb 保證寫操作不會亂序,mb 指令保證了兩者都不會。這些函數都是 barrier函數的超集。
內存屏障出現因爲編譯器或現在的處理器常會自作聰明地對指令序列進行一些處理,比如數據緩存,讀寫指令亂序執 行等等。如果優化對象是普通內存,那麼一般會提升性能而且不會產生邏輯錯誤。但如果對 I/O操作進行類似優化很可能造成致命錯誤。所以要使用內存屏障,以強制該語句前後的指令以正確的次序完成。其實在指令序列中放一個wmb的效果是使得指 令執行到該處時,把所有緩存的數據寫到該寫的地方,同時使得wmb前面的寫指令一定會在wmb的寫指令之前執行。
發佈了25 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章