前言
緩存一致性
順序一致性
內存屏障
多處理器間同步
有了SMP之後,線程就開始同時在多個處理器上運行。只要是線程就有通信和同步的要求。幸好SMP系統是共享內存的,也就是所有處理器看到的內存內容都一樣,雖然有獨立的L1 cache,但還是由硬件完成了緩存一致性處理的問題。那不同處理器上的線程要訪問同一數據,需要臨界區,需要同步。靠什麼同步?之前在UP系統中,我們上靠信號量,下靠關中斷和讀修改寫指令。現在在SMP系統中,關中斷已經廢了,雖然爲了同步同一處理器上的線程還是需要的,但只靠它已經不行了。讀修改寫指令?也不行了。在你指令中讀操作完成寫操作還沒進行時,就可能有另外的處理器進行了讀操作或者寫操作。緩存一致性協議是先進,但還沒有先進到預測這條讀操作是哪種指令發出來的。所以x86又發明了帶lock前綴的指令。在此指令執行時,會將所有包含指令中讀寫地址的cache line失效,並鎖定內存總線。這樣別的處理器要想對同樣的地址或者同一個cache line上的地址讀寫,既無法從cache中進行(cache中相關line已經失效了),也無法從內存總線上進行(整個內存總線都鎖了),終於達到了原子性執行的目的。當然,從P6處理器開始,如果帶lock前綴指令 要訪問的地址本來就在cache中,就無需鎖內存總線,也能完成原子性操作了(雖然我懷疑這是因爲加了多處理器內部公共的L2 cache的緣故)。
內存屏障的實現
#ifdef CONFIG_SMP
#define smp_mb() mb()
#define smp_rmb() rmb()
#define smp_wmb() wmb()
#else
#define smp_mb() barrier()
#define smp_rmb() barrier()
#define smp_wmb() barrier()
#endif
CONFIG_SMP就是用來支持多處理器的。如果是UP(uniprocessor)系統,就會翻譯成barrier()。
#define barrier() __asm__ __volatile__("": : :"memory")
barrier()的作用,就是告訴編譯器,內存的變量值都改變了,之前存在寄存器裏的變量副本無效,要訪問變量還需再訪問內存。這樣做足以滿足UP中所有的內存屏障。
#ifdef CONFIG_X86_32
/*
* Some non-Intel clones support out of order store. wmb() ceases to be a
* nop for these.
*/
#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 wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define mb() asm volatile("mfence":::"memory")
#define rmb() asm volatile("lfence":::"memory")
#define wmb() asm volatile("sfence" ::: "memory")
#endif
如果是SMP系統,內存屏障就會翻譯成對應的mb()、rmb()和wmb()。這裏CONFIG_X86_32的意思是說這是一個32位x86系統,否則就是64位的x86系統。現在的linux內核將32位x86和64位x86融合在同一個x86目錄,所以需要增加這個配置選項。
可以看到,如果是64位x86,肯定有mfence、lfence和sfence三條指令,而32位的x86系統則不一定,所以需要進一步查看cpu是否支持這三條新的指令,不行則用加鎖的方式來增加內存屏障。
SFENCE,LFENCE,MFENCE指令提供了高效的方式來保證讀寫內存的排序,這種操作發生在產生弱排序數據的程序和讀取這個數據的程序之間。
SFENCE——串行化發生在SFENCE指令之前的寫操作但是不影響讀操作。
LFENCE——串行化發生在SFENCE指令之前的讀操作但是不影響寫操作。
MFENCE——串行化發生在MFENCE指令之前的讀寫操作。
sfence:在sfence指令前的寫操作當必須在sfence指令後的寫操作前完成。
lfence:在lfence指令前的讀操作當必須在lfence指令後的讀操作前完成。
mfence:在mfence指令前的讀寫操作當必須在mfence指令後的讀寫操作前完成。
至於帶lock的內存操作,會在鎖內存總線之前,就把之前的讀寫操作結束,功能相當於mfence,當然執行效率上要差一些。
說起來,現在寫點底層代碼真不容易,既要注意SMP問題,又要注意cpu亂序讀寫問題,還要注意cache問題,還有設備DMA問題,等等。