一文搞懂Linux內(nèi)核之內(nèi)存屏障(超級詳細(xì)~)
內(nèi)存屏障(Memory Barriers)
一方面,CPU由于采用指令流水線和超流水線技術(shù),可能導(dǎo)致CPU雖然順序取指令、但有可能會出現(xiàn)“亂序”執(zhí)行的情況,當(dāng)然,對于” a++;b = f(a);c = f”等存在依賴關(guān)系的指令,CPU則會在“b= f(a)”執(zhí)行階段之前被阻塞;另一方面,編譯器也有可能將依賴關(guān)系很近“人為地”拉開距離以防止阻塞情況的發(fā)生,從而導(dǎo)致編譯器亂序,如“a++ ;c = f;b = f(a)”。
一個CPU對指令順序提供如下保證:
On any given CPU, dependent memory accesses will be issued in order, with respect to itself.如Q = P; D = *Q;將保證其順序執(zhí)行
Overlapping loads and stores within a particular CPU will appear to be ordered within that CPU.重疊的Load和Store操作將保證順序執(zhí)行(目標(biāo)地址相同的Load、Store),如:a = *X; *X = b;
It _must_not_ be assumed that independent loads and stores will be issued in the order given.
It _must_ be assumed that overlapping memory accesses may be merged or discarded.如*A = X; Y = *A; => STORE *A = X; Y = LOAD *A; / or STORE *A = Y = X;
由此可見,無關(guān)的內(nèi)存操作會被按隨機順序有效的得到執(zhí)行,但是在CPU與CPU交互時或CPU與IO設(shè)備交互時, 這可能會成為問題. 我們需要一些手段來干預(yù)編譯器和CPU, 使其限制指令順序。內(nèi)存屏障就是這樣的干預(yù)手段. 他們能保證處于內(nèi)存屏障兩邊的內(nèi)存操作滿足部分有序.(譯注: 這里"部分有序"的意思是, 內(nèi)存屏障之前的操作都會先于屏障之后的操作, 但是如果幾個操作出現(xiàn)在屏障的同一邊, 則不保證它們的順序.)
寫(STORE)內(nèi)存屏障。在寫屏障之前的STORE操作將先于所有在寫屏障之后的STORE操作。
數(shù)據(jù)依賴屏障。兩條Load指令,第二條Load指令依賴于第一條Load指令的結(jié)果,則數(shù)據(jù)依賴屏障保障第二條指令的目標(biāo)地址將被更新。
讀(LOAD)內(nèi)存屏障。讀屏障包含數(shù)據(jù)依賴屏障的功能, 并且保證所有出現(xiàn)在屏障之前的LOAD操作都將先于所有出現(xiàn)在屏障之后的LOAD操作被系統(tǒng)中的其他組件所感知.
通用內(nèi)存屏障. 通用內(nèi)存屏障保證所有出現(xiàn)在屏障之前的LOAD和STORE操作都將先于所有出現(xiàn)在屏障之后的LOAD和STORE操作被系統(tǒng)中的其他組件所感知.
LOCK操作.它的作用相當(dāng)于一個單向滲透屏障.它保證所有出現(xiàn)在LOCK之后的內(nèi)存操作都將在LOCK操作被系統(tǒng)中的其他組件所感知之后才能發(fā)生. 出現(xiàn)在LOCK之前的內(nèi)存操作可能在LOCK完成之后才發(fā)生.LOCK操作總是跟UNLOCK操作配對出現(xiàn).
UNLOCK操作。它保證所有出現(xiàn)在UNLOCK之前的內(nèi)存操作都將在UNLOCK操作被系統(tǒng)中的其他組件所感知之前發(fā)生.
LINUX對于x86而言,在為UP體系統(tǒng)架構(gòu)下,調(diào)用barrier()進(jìn)行通用內(nèi)存屏障。在SMP體系架構(gòu)下,若為64位CPU或支持mfence、lfence、sfence指令的32位CPU,則smp_mb()、smp_rmb()、smp_smb()對應(yīng)通用內(nèi)存屏障、寫屏障和讀屏障;而不支持mfence、lfence、sfence指令的32位CPU則smp_mb()、smp_rmb()、smp_smb()對應(yīng)LOCK操作。源碼請參見《內(nèi)存屏障源碼分析》一節(jié)。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ? ? ?


內(nèi)存屏障源碼分析
/include/asm-generic/system.h:
053 #ifdef CONFIG_SMP
054 #define smp_mb() ? ? ? ?mb()
055 #define smp_rmb() ? ? ? rmb()
056 #define smp_wmb() ? ? ? wmb()
057 #else
058 #define smp_mb() ? ? ? ?barrier()
059 #define smp_rmb() ? ? ? barrier()
060 #define smp_wmb() ? ? ? barrier()
061 #endif
在x86 UP體系架構(gòu)中,smp_mb、smp_rmb、smp_wmb被翻譯成barrier:
012 #define barrier() __asm__ __volatile__("": : :"memory")
__volatile告訴編譯器此條語句不進(jìn)行任何優(yōu)化,"": : :"memory" 內(nèi)存單元已被修改、需要重新讀入。
在x86 SMP體系架構(gòu)中,smp_mb、smp_rmb、smp_wmb如下定義:
/arch/x86/include/asm/system.h:
352 /*
353 ?* Force strict CPU ordering.
354 ?* And yes, this is required on UP too when we're talking
355 ?* to devices.
356 ?*/
357 #ifdef CONFIG_X86_32
358 /*
359 ?* Some non-Intel clones support out of order store. wmb() ceases to be a
360 ?* nop for these.
361 ?*/
362 #define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
363 #define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
364 #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
365 #else
366 #define mb() ? ?asm volatile("mfence":::"memory")
367 #define rmb() ? asm volatile("lfence":::"memory")
368 #define wmb() ? asm volatile("sfence" ::: "memory")
369 #endif
362~364行針對x86的32位CPU,366~368行針對x86的64位CPU。
在x86的64位CPU中,mb()宏實際為:
asm volatile("sfence" ::: "memory")。
volatile告訴編譯器嚴(yán)禁在此處匯編語句與其它語句重組優(yōu)化,memory強制編譯器假設(shè)RAM所有內(nèi)存單元均被匯編指令修改,"sfence" ::: 表示在此插入一條串行化匯編指令sfence。
mfence:串行化發(fā)生在mfence指令之前的讀寫操作
lfence:串行化發(fā)生在mfence指令之前的讀操作、但不影響寫操作
sfence:串行化發(fā)生在mfence指令之前的寫操作、但不影響讀操作
在x86的32位CPU中,mb()宏實際為:
mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
由于x86的32位CPU有可能不提供mfence、lfence、sfence三條匯編指令的支持,故在不支持mfence的指令中使用:"lock; addl $0,0(%%esp)", "mfence"。lock表示將“addl $0,0(%%esp)”語句作為內(nèi)存屏障。
關(guān)于lock的實現(xiàn):cpu上有一根pin #HLOCK連到北橋,lock前綴會在執(zhí)行這條指令前先去拉這根pin,持續(xù)到這個指令結(jié)束時放開#HLOCK pin,在這期間,北橋會屏蔽掉一切外設(shè)以及AGP的內(nèi)存操作。也就保證了這條指令的atomic。
