久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

LINUX內(nèi)核內(nèi)存屏障

 langhuayipian 2011-09-29
=================
                         LINUX內(nèi)核內(nèi)存屏障
                         =================


By: David Howells <[email protected]>
    Paul E. McKenney <[email protected]>
譯: kouu <[email protected]>
出 處: Linux內(nèi)核文檔 -- Documentation/memory-barriers.txt


目錄:


 (*) 內(nèi)存訪問抽象模型.


     - 操作設(shè)備.
     - 保證.


 (*) 什么是內(nèi)存屏障?


     - 各式各樣的內(nèi)存屏障.
     - 關(guān)于內(nèi)存屏障, 不能假定什么?
     - 數(shù)據(jù)依賴屏障.
     - 控制依賴.
     - SMP內(nèi)存屏障的配對(duì)使用.
     - 內(nèi)存屏障舉例.
     - 讀內(nèi)存屏障與內(nèi)存預(yù)取.


 (*) 內(nèi)核中顯式的內(nèi)存屏障.


     - 編譯優(yōu)化屏障.
     - CPU內(nèi)存屏障.
     - MMIO寫屏障.


 (*) 內(nèi)核中隱式的內(nèi)存屏障.


     - 鎖相關(guān)函數(shù).
     - 禁止中斷函數(shù).
     - 睡眠喚醒函數(shù).
     - 其他函數(shù).


 (*) 跨CPU的鎖的屏障作用.


     - 鎖與內(nèi)存訪問.
     - 鎖與IO訪問.


 (*) 什么地方需要內(nèi)存屏障?


     - 處理器間交互.
     - 原子操作.
     - 訪問設(shè)備.
     - 中斷.


 (*) 內(nèi)核中I/O屏障的作用.


 (*) 最小限度有序的假想模型.


 (*) CPU cache的影響.


     - Cache一致性.
     - Cache一致性與DMA.
     - Cache一致性與MMIO.


 (*) CPU所能做到的.


     - 特別值得一提的Alpha處理器.


 (*) 使用示例.


     - 環(huán)型緩沖區(qū).


 (*) 引用.




================
內(nèi)存訪問抽象模型
================


考 慮如下抽象系統(tǒng)模型:


                            :                :
                            :                :
                            :                :
                +-------+   :   +--------+   :   +-------+
                |       |   :   |        |   :   |       |
                |       |   :   |        |   :   |       |
                | CPU 1 |<----->|  內(nèi)存  |<----->| CPU 2 |
                |       |   :   |        |   :   |       |
                |       |   :   |        |   :   |       |
                +-------+   :   +--------+   :   +-------+
                    ^       :       ^        :       ^
                    |       :       |        :       |
                    |       :       |        :       |
                    |       :       v        :       |
                    |       :   +--------+   :       |
                    |       :   |        |   :       |
                    |       :   |        |   :       |
                    +---------->|  設(shè)備  |<----------+
                            :   |        |   :
                            :   |        |   :
                            :   +--------+   :
                            :                :


假設(shè)每個(gè)CPU都分別運(yùn)行著一個(gè)會(huì) 觸發(fā)內(nèi)存訪問操作的程序. 對(duì)于這樣一個(gè)CPU, 其內(nèi)存訪問
順序是非常松散的, 在保證程序上下文邏輯關(guān)系的前提下, CPU可以按它所喜歡的順序來執(zhí)
行內(nèi)存操作. 類似的, 編譯器也可以將它輸出的指令安排成任何它喜歡的順序, 只要保證不
影響程序表面 的執(zhí)行邏輯.


(譯注: 


內(nèi)存屏障是為應(yīng)付內(nèi)存訪問操作的亂序執(zhí)行而生的. 那么, 內(nèi)存訪問為什么會(huì)亂序呢? 這里
先簡(jiǎn)要介紹一下:


現(xiàn)在的CPU一般采用流水線來執(zhí)行指令. 一個(gè)指令的執(zhí)行被分成: 取指, 譯碼, 訪存, 執(zhí)行,
寫回, 等若干個(gè)階段. 


指令流水線并不是串行化的, 并不會(huì)因?yàn)橐粋€(gè)耗時(shí)很長(zhǎng)的指令在"執(zhí)行"階段呆很長(zhǎng)時(shí)間, 而
導(dǎo)致后續(xù)的指令都卡在"執(zhí)行"之前的階段上. 


相反, 流水線中的多個(gè)指令是可以同時(shí)處于一個(gè)階段的, 只要CPU內(nèi)部相應(yīng)的處理部件未被
占滿. 比如說CPU有一個(gè)加法器和一個(gè)除法器, 那么一條加法指令和一條除法指令就可能同
時(shí)處于"執(zhí)行"階段, 而兩條加法指令在"執(zhí)行"階段就只能串行工作.


這樣一 來, 亂序可能就產(chǎn)生了. 比如一條加法指令出現(xiàn)在一條除法指令的后面, 但是由于除
法的執(zhí)行時(shí)間很長(zhǎng), 在它執(zhí)行完之前, 加法可能先執(zhí)行完了. 再比如兩條訪存指令, 可能由
于第二條指令命中了cache(或其他原因)而導(dǎo)致它先于第一條指令完成.


一 般情況下, 指令亂序并不是CPU在執(zhí)行指令之前刻意去調(diào)整順序. CPU總是順序的去內(nèi)存里
面取指令, 然后將其順序的放入指令流水線. 但是指令執(zhí)行時(shí)的各種條件, 指令與指令之間
的相互影響, 可能導(dǎo)致順序放入流水線的指令, 最終亂序執(zhí)行完成. 這就是所謂的"順序流
入, 亂序流出".


指令流水線除了在資源不足的情況下會(huì)卡住之外(如前所述的一個(gè)加法器應(yīng)付兩條加法指令)
, 指令之間的相關(guān)性才是導(dǎo)致流水線阻塞的主要原因.


下文中也會(huì)多次提到, CPU的亂序執(zhí)行并不是任意的亂序, 而必須保證上下文依賴邏輯的正
確性. 比如: a++; b=f(a); 由于b=f(a)這條指令依賴于第一條指令(a++)的執(zhí)行結(jié)果, 所以
b=f(a)將在"執(zhí)行"階段之前被阻塞, 直到a++的執(zhí)行結(jié)果被生成出來.


如果兩條像這樣有依賴關(guān)系的指令挨得 很近, 后一條指令必定會(huì)因?yàn)榈却耙粭l執(zhí)行的結(jié)果
, 而在流水線中阻塞很久. 而編譯器的亂序, 作為編譯優(yōu)化的一種手段, 則試圖通過指令重
排 將這樣的兩條指令拉開距離, 以至于后一條指令執(zhí)行的時(shí)候前一條指令結(jié)果已經(jīng)得到了, 
那么也就不再需要阻塞等待了.


相 比于CPU的亂序, 編譯器的亂序才是真正對(duì)指令順序做了調(diào)整. 但是編譯器的亂序也必須
保證程序上下文的依賴邏輯.


由 于指令執(zhí)行存在這樣的亂序, 那么自然, 由指令執(zhí)行而引發(fā)的內(nèi)存訪問勢(shì)必也可能亂序.
)


在上面的圖示中, 一個(gè)CPU執(zhí)行內(nèi)存操作所產(chǎn)生的影響, 一直要到該操作穿越該CPU與系統(tǒng)中
其他部分的界面(見圖中的虛線)之后, 才能被其他部分所感知.


舉 例來說, 考慮如下的操作序列:


        CPU 1           CPU 2
        =============== ===============
        { A == 1; B == 2 }
        A = 3;          x = A;
        B = 4;          y = B;


這一 組訪問指令在內(nèi)存系統(tǒng)(見上圖的中間部分)上生效的順序, 可以有24種不同的組合:


        STORE A=3,   STORE B=4,     x=LOAD A->3,   y=LOAD B->4
        STORE A=3,   STORE B=4,     y=LOAD B->4,   x=LOAD A->3
        STORE A=3,   x=LOAD A->3,   STORE B=4,     y=LOAD B->4
        STORE A=3,   x=LOAD A->3,   y=LOAD B->2,   STORE B=4
        STORE A=3,   y=LOAD B->2,   STORE B=4,     x=LOAD A->3
        STORE A=3,   y=LOAD B->2,   x=LOAD A->3,   STORE B=4
        STORE B=4,   STORE A=3,     x=LOAD A->3,   y=LOAD B->4
        STORE B=4, ...
        ...


然后這就產(chǎn)生四種不同組合的結(jié)果值:


        x == 1, y == 2
        x == 1, y == 4
        x == 3, y == 2
        x == 3, y == 4


甚至于, 一個(gè)CPU在內(nèi)存系統(tǒng)上提交的STORE操作還可能不會(huì)以相同的順序被其他CPU所執(zhí)行
的LOAD操作所感知.


進(jìn)一步舉 例說明, 考慮如下的操作序列:


        CPU 1           CPU 2
        =============== ===============
        { A == 1, B == 2, C = 3, P == &A, Q == &C }
        B = 4;          Q = P;
        P = &B          D = *Q;


這里有一處明顯的數(shù)據(jù)依賴, 因?yàn)樵贑PU2上, LOAD到D里面的值依賴于從P獲取到的地址. 在
操作序列的最后, 下面的幾種結(jié)果都是有可能出現(xiàn)的:


        (Q == &A) 且 (D == 1)
        (Q == &B) 且 (D == 2)
        (Q == &B) 且 (D == 4)


注意, CPU2決不會(huì)將C的值LOAD到D, 因?yàn)镃PU保證在將P的值裝載到Q之后才會(huì)執(zhí)行對(duì)*Q的
LOAD操作(譯注: 因?yàn)榇嬖跀?shù)據(jù)依賴).


操作設(shè)備
--------


對(duì) 于一些設(shè)備, 其控制寄存器被映射到一組內(nèi)存地址集合上, 而這些控制寄存器被訪問的順
序是至關(guān)重要的. 假設(shè), 一個(gè)以太網(wǎng)卡擁有一些內(nèi)部寄存器, 通過一個(gè)地址端口寄存器(A)
和一個(gè)數(shù)據(jù)端口寄存器(D)來訪問它們. 要讀取編號(hào)為5的內(nèi)部寄存器, 可能使用如下代碼:
        *A = 5;
        x = *D;


但是這可能會(huì)表現(xiàn)為以下兩個(gè) 序列之一(譯注: 因?yàn)閺某绦虮砻婵? A和D是不存在依賴的):


        STORE *A = 5, x = LOAD *D
        x = LOAD *D, STORE *A = 5


其中的第二種幾乎肯定會(huì)導(dǎo)致錯(cuò)誤, 因?yàn)樗谧x取寄存器之后才設(shè)置寄存器的編號(hào).


保證
----


對(duì)于一個(gè)CPU, 它最低限度會(huì)提供如下的保證:


 (*) 對(duì)于一個(gè)CPU, 在它上面出現(xiàn)的有上下文依賴關(guān)系的內(nèi)存訪問將被按順序執(zhí)行. 這意味
     著:


        Q = P; D = *Q;


     CPU會(huì)順序執(zhí)行以下訪存:


        Q = LOAD P, D = LOAD *Q


     并且總是按這樣的順序.


 (*) 對(duì)于一個(gè)CPU, 重疊的LOAD和STORE操作將被按順序執(zhí)行. 這意味著:


        a = *X; *X = b;


     CPU只會(huì)按以下順序執(zhí)行訪存:


        a = LOAD *X, STORE *X = b


     同樣, 對(duì)于:


        *X = c; d = *X;


     CPU只會(huì)按以下順序執(zhí)行訪存:


        STORE *X = c, d = LOAD *X


     (如果LOAD和STORE的目標(biāo)指向同一塊內(nèi)存地址, 則認(rèn)為是重疊).


還有一些事情是必須被假定或者必須不被假定的:


 (*) 必須不能假定無關(guān)的LOAD和STORE會(huì)按給定的順序被執(zhí)行. 這意味著:


        X = *A; Y = *B; *D = Z;


     可能會(huì)得到如下幾種執(zhí)行序列之一:


        X = LOAD *A,  Y = LOAD *B,  STORE *D = Z
        X = LOAD *A,  STORE *D = Z, Y = LOAD *B
        Y = LOAD *B,  X = LOAD *A,  STORE *D = Z
        Y = LOAD *B,  STORE *D = Z, X = LOAD *A
        STORE *D = Z, X = LOAD *A,  Y = LOAD *B
        STORE *D = Z, Y = LOAD *B,  X = LOAD *A


 (*) 必須假定重疊內(nèi)存訪問可能被合并或丟棄. 這意味著:


        X = *A; Y = *(A + 4);


     可能會(huì)得到如下幾種執(zhí)行序列之一:


        X = LOAD *A; Y = LOAD *(A + 4);
        Y = LOAD *(A + 4); X = LOAD *A;
        {X, Y} = LOAD {*A, *(A + 4) };


     同樣, 對(duì)于:


        *A = X; Y = *A;


     可能會(huì)得到如下幾種執(zhí)行序列之一:


        STORE *A = X; Y = LOAD *A;
        STORE *A = Y = X;




===============
什么是內(nèi)存屏障?
===============


正 如上面所說, 無關(guān)的內(nèi)存操作會(huì)被按隨機(jī)順序有效的得到執(zhí)行, 但是在CPU與CPU交互時(shí)或
CPU與IO設(shè)備交互時(shí), 這可能會(huì)成為問題. 我們需要一些手段來干預(yù)編譯器和CPU, 使其限制
指令順序.


內(nèi)存屏障就是這樣的干預(yù)手段. 他們能保證處于內(nèi)存屏障兩邊的內(nèi)存操作滿足部分有序. (
譯注: 這里"部分有序"的意思是, 內(nèi)存屏障之前的操作都會(huì)先于屏障之后的操作, 但是如果
幾個(gè)操作出現(xiàn)在屏障的同一邊, 則不保證它們的順序. 這一點(diǎn)下文將多次提到.)


這樣的強(qiáng)制措施是非常重要的, 因?yàn)橄到y(tǒng)中的CPU和其他設(shè)備可以使用各種各樣的策略來提
高性能, 包括對(duì)內(nèi)存操作的亂序, 延遲和合并執(zhí)行; 預(yù)取; 投機(jī)性的分支預(yù)測(cè)和各種緩存. 
內(nèi)存屏障用于禁用或抑制這些策略, 使代碼能夠清楚的控制多個(gè)CPU和/或設(shè)備的交互.


各 式各樣的內(nèi)存屏障
------------------


內(nèi)存屏障有四種基本類型:


 (1) 寫(STORE)內(nèi)存屏障.


     寫內(nèi)存屏障提供這樣的保證: 所有出現(xiàn)在屏障之前的STORE操作都將先于所有出現(xiàn)在屏
     障之后的STORE操作被系統(tǒng)中的其他組件所感知.


     寫屏障僅保證針對(duì)STORE操作的部分有序; 不要求對(duì)LOAD操作產(chǎn)生影響.


     隨著時(shí)間的推移, 一個(gè)CPU提交的STORE操作序列將被存儲(chǔ)系統(tǒng)所感知. 所有在寫屏障
     之前的STORE操作將先于所有在寫屏障之后的STORE操作出現(xiàn)在被感知的序列中.


     [!] 注意, 寫屏障一般需要與讀屏障或數(shù)據(jù)依賴屏障配對(duì)使用; 參閱"SMP內(nèi)存屏障配
         對(duì)"章節(jié). (譯注: 因?yàn)閷懫琳现槐WC自己提交的順序, 而無法干預(yù)其他代碼讀內(nèi)
         存的順序. 所以配對(duì)使用很重要. 其他類型的屏障亦是同理.)


 (2) 數(shù)據(jù)依賴屏障.


     數(shù)據(jù)依賴屏障是讀屏障的弱化版本. 假設(shè)有兩個(gè)LOAD操作的場(chǎng)景, 其中第二個(gè)LOAD操
     作的結(jié)果依賴于第一個(gè)操作(比如, 第一個(gè)LOAD獲取地址, 而第二個(gè)LOAD使用該地址去
     取數(shù)據(jù)), 數(shù)據(jù)依賴屏障確保在第一個(gè)LOAD獲取的地址被用于訪問之前, 第二個(gè)LOAD的
     目標(biāo)內(nèi)存已經(jīng)更新.
     (譯注: 因?yàn)榈诙€(gè)LOAD要使用第一個(gè)LOAD的結(jié)果來作為L(zhǎng)OAD的目標(biāo), 這里存在著數(shù)
     據(jù)依賴. 由前面的"保證"章節(jié)可知, 第一個(gè)LOAD必定會(huì)在第二個(gè)LOAD之前執(zhí)行, 不需
     要使用讀屏障來保證順序, 只需要使用數(shù)據(jù)依賴屏障來保證內(nèi)存已刷新.)


     數(shù)據(jù)依賴屏障僅保證針對(duì)相互依賴的LOAD操作的部分有序; 不要求對(duì)STORE操作, 
     獨(dú)立的LOAD操作, 或重疊的LOAD操作產(chǎn)生影響.


     正如(1)中所提到的, 在一個(gè)CPU看來, 系統(tǒng)中的其他CPU提交到內(nèi)存系統(tǒng)的STORE操作
     序列在某一時(shí)刻可以被其感知到. 而在該CPU上觸發(fā)的數(shù)據(jù)依賴屏障將保證, 對(duì)于在屏
     障之前發(fā)生的LOAD操作, 如果一個(gè)LOAD操作的目標(biāo)被其他CPU的STORE操作所修改, 那
     么在屏障完成之時(shí), 這個(gè)對(duì)應(yīng)的STORE操作之前的所有STORE操作所產(chǎn)生的影響, 將被
     數(shù)據(jù)依賴屏障之后執(zhí)行的LOAD操作所感知.


     參閱"內(nèi)存屏障舉例"章節(jié)所描述的時(shí)序圖.


     [!] 注意, 對(duì)第一個(gè)LOAD的依賴的確是一個(gè)數(shù)據(jù)依賴而不是控制依賴. 而如果第二個(gè)
         LOAD的地址依賴于第一個(gè)LOAD, 但并不是通過實(shí)際加載的地址本身這樣的依賴條
         件, 那么這就是控制依賴, 需要一個(gè)完整的讀屏障或更強(qiáng)的屏障. 參閱"控制依
         賴"相關(guān)章節(jié).


     [!] 注意, 數(shù)據(jù)依賴屏障一般要跟寫屏障配對(duì)使用; 參閱"SMP內(nèi)存屏障的配對(duì)使用"章
         節(jié).


 (3) 讀(LOAD)內(nèi)存屏障.


     讀屏障包含數(shù)據(jù)依賴屏障的功能, 并且保證所有出現(xiàn)在屏障之前的LOAD操作都將先于
     所有出現(xiàn)在屏障之后的LOAD操作被系統(tǒng)中的其他組件所感知.


     讀屏障僅保證針對(duì)LOAD操作的部分有序; 不要求對(duì)STORE操作產(chǎn)生影響.


     讀內(nèi)存屏障隱含了數(shù)據(jù)依賴屏障, 因此可以用于替代它們.


     [!] 注意, 讀屏障一般要跟寫屏障配對(duì)使用; 參閱"SMP內(nèi)存屏障的配對(duì)使用"章節(jié).


 (4) 通用內(nèi)存屏障.


     通用內(nèi)存屏障保證所有出現(xiàn)在屏障之前的LOAD和STORE操作都將先于所有出現(xiàn)在屏障
     之后的LOAD和STORE操作被系統(tǒng)中的其他組件所感知.


     通用內(nèi)存屏障是針對(duì)LOAD和STORE操作的部分有序.


     通用內(nèi)存屏障隱含了讀屏障和寫屏障, 因此可以用于替代它們.


內(nèi)存屏障還有兩種隱式類型:


 (5) LOCK操作.


     它的作用相當(dāng)于一個(gè)單向滲透屏障. 它保證所有出現(xiàn)在LOCK之后的內(nèi)存操作都將在
     LOCK操作被系統(tǒng)中的其他組件所感知之后才能發(fā)生.


     出現(xiàn)在LOCK之前的內(nèi)存操作可能在LOCK完成之后才發(fā)生.


     LOCK操作總是跟UNLOCK操作配對(duì)出現(xiàn)的.


 (6) UNLOCK操作.


     它的作用也相當(dāng)于一個(gè)單向滲透屏障. 它保證所有出現(xiàn)在UNLOCK之前的內(nèi)存操作都將
     在UNLOCK操作被系統(tǒng)中的其他組件所感知之前發(fā)生.


     出現(xiàn)在UNLOCK之后的內(nèi)存操作可能在UNLOCK完成之前就發(fā)生了.


     需要保證LOCK和UNLOCK操作嚴(yán)格按照相互影響的正確順序出現(xiàn).


     (譯注: LOCK和UNLOCK的這種單向屏障作用, 確保臨界區(qū)內(nèi)的訪存操作不能跑到臨界區(qū)
     外, 否則就起不到"保護(hù)"作用了.)
     使用LOCK和UNLOCK之后, 一般就不再需要其他內(nèi)存屏障了(但是注意"MMIO寫屏障"章節(jié)
     中所提到的例外).


只 有在存在多CPU交互或CPU與設(shè)備交互的情況下才可能需要用到內(nèi)存屏障. 如果可以確保某
段代碼中不存在這樣的交互, 那么這段代碼就不需要使用內(nèi)存屏障. (譯注: CPU亂序執(zhí)行指
令, 同樣會(huì)導(dǎo)致寄存器的存取順序被打亂, 但是為什么不需要寄存器屏障呢? 就是因?yàn)榧拇?br>器是CPU私有的, 不存在跟其他CPU或設(shè)備的交互.)


注意, 對(duì)于前面提到的最低限度保證. 不同的體系結(jié)構(gòu)可能提供更多的保證, 但是在特定體
系結(jié)構(gòu)的代碼之外, 不能依賴于這些額外的保證.


關(guān)于內(nèi)存屏障, 不能假定什么?
---------------------------


Linux內(nèi)核的內(nèi)存屏障不保證下面這些事情:


 (*) 在內(nèi)存屏障之前出現(xiàn)的內(nèi)存訪問不保證在內(nèi)存屏障指令完成之前完成; 內(nèi)存屏障相當(dāng)
     于在該CPU的訪問隊(duì)列中畫一條線, 使得相關(guān)訪存類型的請(qǐng)求不能相互跨越. (譯注:
     用于實(shí)現(xiàn)內(nèi)存屏障的指令, 其本身并不作為參考對(duì)象, 其兩邊的訪存操作才被當(dāng)作參
     考對(duì)象. 所以屏障指令執(zhí)行完成并不表示出現(xiàn)在屏障之前的訪存操作已經(jīng)完成. 而如
     果屏障之后的某一個(gè)訪存操作已經(jīng)完成, 則屏障之前的所有訪存操作必定都已經(jīng)完成
     了.)


 (*) 在一個(gè)CPU上執(zhí)行的內(nèi)存屏障不保證會(huì)直接影響其他系統(tǒng)中的CPU或硬件設(shè)備. 只會(huì)間
     接影響到第二個(gè)CPU感知第一個(gè)CPU產(chǎn)生訪存效果的順序, 不過請(qǐng)看下一點(diǎn):


 (*) 不能保證一個(gè)CPU能夠按順序看到另一個(gè)CPU的訪存效果, 即使另一個(gè)CPU使用了內(nèi)存屏
     障, 除非這個(gè)CPU也使用了與之配對(duì)的內(nèi)存屏障(參閱"SMP內(nèi)存屏障的配對(duì)使用"章節(jié)).


 (*) 不保證一些與CPU相關(guān)的硬件不會(huì)亂序訪存. CPU cache一致性機(jī)構(gòu)會(huì)在CPU之間傳播內(nèi)
     存屏障所帶來的間接影響, 但是可能不是按順序的.


        [*] 更多關(guān)于總線主控DMA和一致性的問題請(qǐng)參閱:


            Documentation/PCI/pci.txt
            Documentation/PCI/PCI-DMA-mapping.txt
            Documentation/DMA-API.txt


數(shù)據(jù)依賴屏障
------------


數(shù) 據(jù)依賴屏障的使用需求有點(diǎn)微妙, 并不總是很明顯就能看出需要他們. 為了說明這一點(diǎn), 
考慮如下的操作序列:


        CPU 1           CPU 2
        =============== ===============
        { A == 1, B == 2, C = 3, P == &A, Q == &C }
        B = 4;
        <寫屏障>
        P = &B
                        Q = P;
                        D = *Q;


這里有明顯的數(shù)據(jù)依賴, 在序列執(zhí)行完之后, Q的值一定是&A和&B之一, 也就是:


        (Q == &A) 那么 (D == 1)
        (Q == &B) 那么 (D == 4)


但是! CPU 2可能在看到P被更新之后, 才看到B被更新, 這就導(dǎo)致下面的情況:


        (Q == &B) 且 (D == 2) ????


雖然這看起來似乎是一個(gè)一致性錯(cuò)誤或邏輯關(guān)系錯(cuò)誤, 但其實(shí)不是, 并且在一些真實(shí)的CPU
中就能 看到這樣的行為(就比如DEC Alpha).


為了解決這個(gè)問題, 必須在取地址和取數(shù)據(jù)之間插入一個(gè)數(shù)據(jù)依賴或更強(qiáng)的屏障:


        CPU 1           CPU 2
        =============== ===============
        { A == 1, B == 2, C = 3, P == &A, Q == &C }
        B = 4;
        <寫屏障>
        P = &B
                        Q = P;
                        <數(shù)據(jù)依賴屏障>
                        D = *Q;


這將強(qiáng)制最終結(jié)果是前兩種情況之一, 而避免出現(xiàn)第三種情況.


[!] 注意, 這種非常違反直覺的情況最容易出現(xiàn)在cache分列的機(jī)器上, 比如, 一個(gè)cache組
    處理偶數(shù)號(hào)的cache行, 另一個(gè)cache組處理奇數(shù)號(hào)的cache行. P指針可能存儲(chǔ)在奇數(shù)號(hào)
    的cache行中, 而B的值可能存儲(chǔ)在偶數(shù)號(hào)的cache行中. 這樣一來, 如果執(zhí)行讀操作的
    CPU的偶數(shù)號(hào)cache組非常繁忙, 而奇數(shù)號(hào)cache組空閑, 它就可能看到P已被更新成新值
    (&B), 而B還是舊值(2).


另一個(gè)可 能需要數(shù)據(jù)依賴屏障的例子是, 從內(nèi)存讀取一個(gè)數(shù)值, 用于計(jì)算數(shù)組的訪問偏移:


        CPU 1           CPU 2
        =============== ===============
        { M[0] == 1, M[1] == 2, M[3] = 3, P == 0, Q == 3 }
        M[1] = 4;
        <寫屏障>
        P = 1
                        Q = P;
                        <數(shù)據(jù)依賴屏障>
                        D = M[Q];


數(shù) 據(jù)依賴屏障對(duì)于RCU非常重要, 舉例來說. 參閱include/linux/rcupdate.h文件中的
rcu_dereference() 函數(shù). 這個(gè)函數(shù)使得當(dāng)前RCU指針指向的對(duì)象被替換成新的對(duì)象時(shí), 不會(huì)
發(fā)生新對(duì)象尚未初始化完成的情況. (譯注: 更新RCU對(duì)象時(shí), 一般步驟是: 1-為新對(duì)象分配
空間; 2-初始化新對(duì)象; 3-調(diào)用rcu_dereference()函數(shù), 將對(duì)象指針指到新的對(duì)象上, 這
就 意味著新的對(duì)象已生效. 這個(gè)過程中如果出現(xiàn)亂序訪存, 可能導(dǎo)致對(duì)象指針的更新發(fā)生在
新對(duì)象初始化完成之前. 也就是說, 新對(duì)象尚未初始化完成就已經(jīng)生效了. 那么別的CPU就
可能引用到一個(gè)尚未初始化完成的新對(duì)象, 從而出現(xiàn)錯(cuò)誤.)


更詳 盡的例子請(qǐng)參閱"Cache一致性"章節(jié).


控制依賴
--------


控制依賴需要使用一個(gè)完整 的讀內(nèi)存屏障, 簡(jiǎn)單的數(shù)據(jù)依賴屏障不能使其正確工作. 考慮
下面的代碼:


        q = &a;
        if (p)
                q = &b;
        <數(shù)據(jù)依賴屏障>
        x = *q;


這段代碼可能達(dá)不到預(yù)期的效果, 因?yàn)檫@里其實(shí)并不是數(shù)據(jù)依賴, 而是控制依賴, CPU可能
試 圖通過提前預(yù)測(cè)結(jié)果而對(duì)"if (p)"進(jìn)行短路. 在這樣的情況下, 需要的是:


        q = &a;
        if (p)
                q = &b;
        <讀屏障>
        x = *q;


(譯注:
例如:
        CPU 1           CPU 2
        =============== ===============
        { a == 1, b == 2, p == 0}
        a = 3;
        b = 4;
        <寫屏障>
        p = 1;
                        q = &a;
                        if (p)
                                q = &b;
                        <數(shù)據(jù)依賴屏障>
                        x = *q;


CPU 1上的寫屏障是為了保證這樣的邏輯: 如果p == 1, 那么必定有a == 3 && b == 4.
但是到了CPU 2, 可能p的值已更新(==1), 而a和b的值未更新, 那么這時(shí)數(shù)據(jù)依賴屏障可以
起作用, 確保x = *q時(shí)a和b的值更新. 因?yàn)閺拇a邏輯上說, q跟a或b是有所依賴的, 數(shù)據(jù)
依賴屏障能保證這些有依賴關(guān)系的值都已更新.


然而, 換一個(gè)寫法:
        CPU 1           CPU 2
        =============== ===============
        { a == 1, b == 2, p == 0}
        p = 1;
        <寫屏障>
        a = 3;
        b = 4;
                        q = &a;
                        if (p)
                                q = &b;
                        <讀屏障>
                        x = *q;


CPU 1上的寫屏障是為了保證這樣的邏輯: 如果a == 3 || b == 4, 那么必定有p == 1.
但是到了CPU 2, 可能a或b的值已更新, 而p的值未更新. 那么這時(shí)使用數(shù)據(jù)依賴屏障就不能
保證p的更新. 因?yàn)閺拇a邏輯上說, p跟任何人都沒有依賴關(guān)系. 這時(shí)必須使用讀屏障, 以
確保x = *q之前, p被更新.
原文中"短路"的意思就是, 由于p沒有數(shù)據(jù)依賴關(guān)系, CPU可以早早獲得它的值, 而不必考慮
更新.)


SMP 內(nèi)存屏障的配對(duì)使用
---------------------


在處理CPU與CPU的交互時(shí), 對(duì)應(yīng)類型的內(nèi)存屏障總是應(yīng)該配對(duì)使用. 缺乏適當(dāng)配對(duì)的使用基
本上可以肯定是錯(cuò)誤的.


一個(gè)寫屏障總是與一個(gè)數(shù)據(jù)依賴屏障 或讀屏障相配對(duì), 雖然通用屏障也可行. 類似的, 一個(gè)
讀屏障或數(shù)據(jù)依賴屏障也總是與一個(gè)寫屏障相配對(duì), 盡管一個(gè)通用屏障也同樣可行:


        CPU 1           CPU 2
        =============== ===============
        a = 1;
        <寫屏障>
        b = 2;          x = b;
                        <讀屏障>
                        y = a;


或:


        CPU 1           CPU 2
        =============== ===============
        a = 1;
        <寫屏障>
        b = &a;         x = b;
                        <數(shù)據(jù)依賴屏障>
                        y = *x;


基本上, 讀屏障總是需要用在這些地方的, 盡管可以使用"弱"類型.


[!] 注意, 在寫屏障之前出現(xiàn)的STORE操作通??偸瞧谕ヅ渥x屏障或數(shù)據(jù)依賴屏障之后出
現(xiàn)的LOAD操作, 反之亦然:


        CPU 1                       CPU 2
        ===============             ===============
        a = 1;       }----   --->{  v = c
        b = 2;       }    \ /    {  w = d
        <寫屏障>           \        <讀屏障>
        c = 3;       }    / \    {  x = a;
        d = 4;       }----   --->{  y = b;


內(nèi)存屏障舉例
------------


首 先, 寫屏障用作部分有序的STORE操作. 考慮如下的操作序列:


        CPU 1
        =======================
        STORE A = 1
        STORE B = 2
        STORE C = 3
        <寫屏障>
        STORE D = 4
        STORE E = 5


這個(gè)操作序列會(huì)按順序被提交到內(nèi)存一致性系統(tǒng), 而系統(tǒng)中的其他組件可能看到
{ STORE A, STORE B, STORE C }的組合出現(xiàn)在{ STORE D, STORE E }的組合之前, 而組合
內(nèi)部可能亂序:


        +-------+       :      :
        |       |       +------+
        |       |------>| C=3  |     }     /\
        |       |  :    +------+     }-----  \  -----> 操作被系統(tǒng)中的其他
        |       |  :    | A=1  |     }        \/       組件所感知
        |       |  :    +------+     }
        | CPU 1 |  :    | B=2  |     }
        |       |       +------+     }
        |       |   wwwwwwwwwwwwwwww }   <--- 在這一時(shí)刻, 寫屏障要求在它之
        |       |       +------+     }        前出現(xiàn)的STORE操作都先于在它
        |       |  :    | E=5  |     }        之后出現(xiàn)的STORE操作被提交
        |       |  :    +------+     }
        |       |------>| D=4  |     }
        |       |       +------+
        +-------+       :      :
                           |
                           | CPU 1發(fā)起的STORE操作被提交到內(nèi)存系統(tǒng)的順序
                           |
                           V


其次, 數(shù)據(jù)依賴屏障用作部分有序的數(shù)據(jù)依賴LOAD操作. 考慮如下的操作序列:


        CPU 1                        CPU 2
        =======================      =======================
        { B = 7; X = 9; Y = 8; C = &Y }
        STORE A = 1
        STORE B = 2
        <寫屏障>
        STORE C = &B                 LOAD X
        STORE D = 4                  LOAD C (得到&B)
                                     LOAD *C (讀取B)


沒有干預(yù)的話, CPU 1的操作被CPU 2感知到的順序是隨機(jī)的, 盡管CPU 1執(zhí)行了寫屏障:


        +-------+       :      :                :       :
        |       |       +------+                +-------+  | CPU 2所看到的
        |       |------>| B=2  |-----       --->| Y->8  |  | 更新序列
        |       |  :    +------+     \          +-------+  |
        | CPU 1 |  :    | A=1  |      \     --->| C->&Y |  V
        |       |       +------+       |        +-------+
        |       |   wwwwwwwwwwwwwwww   |        :       :
        |       |       +------+       |        :       :
        |       |  :    | C=&B |---    |        :       :       +-------+
        |       |  :    +------+   \   |        +-------+       |       |
        |       |------>| D=4  |    ----------->| C->&B |------>|       |
        |       |       +------+       |        +-------+       |       |
        +-------+       :      :       |        :       :       |       |
                                       |        :       :       |       |
                                       |        :       :       | CPU 2 |
                                       |        +-------+       |       |
            對(duì)B的取值顯然不正確  --->  |        | B->7  |------>|       |
                                       |        +-------+       |       |
                                       |        :       :       |       |
                                       |        +-------+       |       |
            對(duì)X的LOAD延誤了B的  --->    \       | X->9  |------>|       |
            一致性更新                   \      +-------+       |       |
                                          ----->| B->2  |       +-------+
                                                +-------+
                                                :       :


在 上面的例子中, CPU 2看到的B的值是7, 盡管對(duì)*C(值應(yīng)該是B)的LOAD發(fā)生在對(duì)C的LOAD之
后.


但是, 如果一個(gè)數(shù)據(jù)依賴屏障被放到CPU 2的LOAD C和LOAD *C(假設(shè)值是B)之間:


        CPU 1                        CPU 2
        =======================      =======================
        { B = 7; X = 9; Y = 8; C = &Y }
        STORE A = 1
        STORE B = 2
        <寫屏障>
        STORE C = &B                 LOAD X
        STORE D = 4                  LOAD C (獲得&B)
                                     <數(shù)據(jù)依賴屏障>
                                     LOAD *C (讀取B)


那 么下面的情況將會(huì)發(fā)生:


        +-------+       :      :                :       :
        |       |       +------+                +-------+
        |       |------>| B=2  |-----       --->| Y->8  |
        |       |  :    +------+     \          +-------+
        | CPU 1 |  :    | A=1  |      \     --->| C->&Y |
        |       |       +------+       |        +-------+
        |       |   wwwwwwwwwwwwwwww   |        :       :
        |       |       +------+       |        :       :
        |       |  :    | C=&B |---    |        :       :       +-------+
        |       |  :    +------+   \   |        +-------+       |       |
        |       |------>| D=4  |    ----------->| C->&B |------>|       |
        |       |       +------+       |        +-------+       |       |
        +-------+       :      :       |        :       :       |       |
                                       |        :       :       |       |
                                       |        :       :       | CPU 2 |
                                       |        +-------+       |       |
                                       |        | X->9  |------>|       |
                                       |        +-------+       |       |
          確保STORE C之前的影響  --->   \   ddddddddddddddddd   |       |
          都被后續(xù)的LOAD操作感           \      +-------+       |       |
          知到                            ----->| B->2  |------>|       |
                                                +-------+       |       |
                                                :       :       +-------+


第三, 讀屏障用作部分有序的LOAD操作. 考慮如下事件序列:


        CPU 1                        CPU 2
        =======================      =======================
        { A = 0, B = 9 }
        STORE A=1
        <寫屏障>
        STORE B=2
                                     LOAD B
                                     LOAD A


沒有干預(yù)的話, CPU 1的操作被CPU 2感知到的順序是隨機(jī)的, 盡管CPU 1執(zhí)行了寫屏障:


        +-------+       :      :                :       :
        |       |       +------+                +-------+
        |       |------>| A=1  |------      --->| A->0  |
        |       |       +------+      \         +-------+
        | CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
        |       |       +------+        |       +-------+
        |       |------>| B=2  |---     |       :       :
        |       |       +------+   \    |       :       :       +-------+
        +-------+       :      :    \   |       +-------+       |       |
                                     ---------->| B->2  |------>|       |
                                        |       +-------+       | CPU 2 |
                                        |       | A->0  |------>|       |
                                        |       +-------+       |       |
                                        |       :       :       +-------+
                                         \      :       :
                                          \     +-------+
                                           ---->| A->1  |
                                                +-------+
                                                :       :


但是, 如果一個(gè)讀屏障被放到CPU 2的LOAD B和LOAD A之間:


        CPU 1                        CPU 2
        =======================      =======================
        { A = 0, B = 9 }
        STORE A=1
        <寫屏障>
        STORE B=2
                                     LOAD B
                                     <讀屏障>
                                     LOAD A


那么CPU 1所施加的部分有序?qū)⒄_的被CPU 2所感知:


        +-------+       :      :                :       :
        |       |       +------+                +-------+
        |       |------>| A=1  |------      --->| A->0  |
        |       |       +------+      \         +-------+
        | CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
        |       |       +------+        |       +-------+
        |       |------>| B=2  |---     |       :       :
        |       |       +------+   \    |       :       :       +-------+
        +-------+       :      :    \   |       +-------+       |       |
                                     ---------->| B->2  |------>|       |
                                        |       +-------+       | CPU 2 |
                                        |       :       :       |       |
                                        |       :       :       |       |
          在這一時(shí)刻, 讀屏障導(dǎo)致 ---->   \  rrrrrrrrrrrrrrrrr   |       |
          STORE B之前的影響都被           \     +-------+       |       |
          CPU 2所感知                      ---->| A->1  |------>|       |
                                                +-------+       |       |
                                                :       :       +-------+


為 了更全面地說明這一點(diǎn), 考慮一下如果代碼在讀屏障的兩邊都有一個(gè)LOAD A的話, 會(huì)發(fā)生
什么:


        CPU 1                        CPU 2
        =======================      =======================
        { A = 0, B = 9 }
        STORE A=1
        <寫屏障>
        STORE B=2
                                     LOAD B
                                     LOAD A [第一次LOAD A]
                                     <讀屏障>
                                     LOAD A [第二次LOAD A]


盡管兩次LOAD A都發(fā)生在LOAD B之后, 它們也可能得到不同的值:


        +-------+       :      :                :       :
        |       |       +------+                +-------+
        |       |------>| A=1  |------      --->| A->0  |
        |       |       +------+      \         +-------+
        | CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
        |       |       +------+        |       +-------+
        |       |------>| B=2  |---     |       :       :
        |       |       +------+   \    |       :       :       +-------+
        +-------+       :      :    \   |       +-------+       |       |
                                     ---------->| B->2  |------>|       |
                                        |       +-------+       | CPU 2 |
                                        |       :       :       |       |
                                        |       :       :       |       |
                                        |       +-------+       |       |
                                        |       | A->0  |------>| 一次  |
                                        |       +-------+       |       |
          在這一時(shí)刻, 讀屏障導(dǎo)致 ---->   \  rrrrrrrrrrrrrrrrr   |       |
          STORE B之前的影響都被           \     +-------+       |       |
          CPU 2所感知                      ---->| A->1  |------>| 二次  |
                                                +-------+       |       |
                                                :       :       +-------+


但是也可能CPU 2在讀屏障結(jié)束之前就感知到CPU 1對(duì)A的更新:


        +-------+       :      :                :       :
        |       |       +------+                +-------+
        |       |------>| A=1  |------      --->| A->0  |
        |       |       +------+      \         +-------+
        | CPU 1 |   wwwwwwwwwwwwwwww   \    --->| B->9  |
        |       |       +------+        |       +-------+
        |       |------>| B=2  |---     |       :       :
        |       |       +------+   \    |       :       :       +-------+
        +-------+       :      :    \   |       +-------+       |       |
                                     ---------->| B->2  |------>|       |
                                        |       +-------+       | CPU 2 |
                                        |       :       :       |       |
                                         \      :       :       |       |
                                          \     +-------+       |       |
                                           ---->| A->1  |------>| 一次  |
                                                +-------+       |       |
                                            rrrrrrrrrrrrrrrrr   |       |
                                                +-------+       |       |
                                                | A->1  |------>| 二次  |
                                                +-------+       |       |
                                                :       :       +-------+


這里只保證, 如果LOAD B得到的值是2的話, 第二個(gè)LOAD A能得到的值是1. 對(duì)于第一個(gè)
LOAD A是不存在這樣的保證的; 它可能得到A的值是0或是1.


讀內(nèi)存屏障與內(nèi)存預(yù)取
--------------------


許 多CPU會(huì)對(duì)LOAD操作進(jìn)行預(yù)取: 作為性能優(yōu)化的一種手段, 當(dāng)CPU發(fā)現(xiàn)它們將要從內(nèi)存LOAD
一個(gè)數(shù)據(jù)時(shí), 它們會(huì)尋找一個(gè)不需要使用總線來進(jìn)行其他LOAD操作的時(shí)機(jī), 用于LOAD這個(gè)數(shù)
據(jù) - 盡管他們的指令執(zhí)行流程實(shí)際上還沒有到達(dá)該處LOAD的地方. 實(shí)際上, 這可能使得某
些LOAD指令能夠立即完成, 因?yàn)镃PU已經(jīng)預(yù)取到了所需要LOAD的值.


這也可能出現(xiàn)CPU實(shí)際上用不到這個(gè)預(yù)取的值的情況 - 可能因?yàn)橐粋€(gè)分支而避開了這次LOAD
 - 在這樣的情況下, CPU可以丟棄這個(gè)值或者干脆就緩存它以備后續(xù)使用.


考 慮如下場(chǎng)景:


        CPU 1                   CPU 2
        ======================= =======================
                                LOAD B
                                DIVIDE   } 除法指令通常消耗
                                DIVIDE   } 很長(zhǎng)的執(zhí)行時(shí)間
                                LOAD A


這可能將表現(xiàn)為如下情況:


                                  :       :       +-------+
                                  +-------+       |       |
                              --->| B->2  |------>|       |
                                  +-------+       | CPU 2 |
                                  :       :DIVIDE |       |
                                  +-------+       |       |
        CPU在執(zhí)行除法指令 ---> -->| A->0  |~~~~   |       |
        的同時(shí), 預(yù)取A             +-------+   ~   |       |
        (譯注: 此時(shí)總線空閑)      :       :   ~   |       |
                                  :       :DIVIDE |       |
                                  :       :   ~   |       |
           一旦除法結(jié)束,  -->     :       :   ~-->|       |
           CPU能馬上使            :       :       |       |
           LOAD指令生效           :       :       +-------+


如果在第二個(gè)LOAD之前放一個(gè)讀屏障或數(shù)據(jù)依賴屏障:


        CPU 1                   CPU 2
        ======================= =======================
                                LOAD B
                                DIVIDE
                                DIVIDE
                                <讀屏障>
                                LOAD A


這在一定程度上將迫使預(yù)取所獲得的值, 根據(jù)屏障的類型而被重新考慮. 如果沒有新的更新
操作作用到已經(jīng)被預(yù)取的內(nèi)存地址, 則預(yù)取到的值就會(huì)被使用:


                                  :       :       +-------+
                                  +-------+       |       |
                              --->| B->2  |------>|       |
                                  +-------+       | CPU 2 |
                                  :       :DIVIDE |       |
                                  +-------+       |       |
        CPU在執(zhí)行除法指令 ---> -->| A->0  |~~~~   |       |
        的同時(shí), 預(yù)取A             +-------+   ~   |       |
                                  :       :   ~   |       |
                                  :       :DIVIDE |       |
                                  :       :   ~   |       |
                                  :       :   ~   |       |
                              rrrrrrrrrrrrrrrr~   |       |
                                  :       :   ~   |       |
                                  :       :   ~-->|       |
                                  :       :       |       |
                                  :       :       +-------+


但是, 如果存在一個(gè)來自于其他CPU的更新或失效, 那么預(yù)取將被取消, 并且重新載入值:


                                  :       :       +-------+
                                  +-------+       |       |
                              --->| B->2  |------>|       |
                                  +-------+       | CPU 2 |
                                  :       :DIVIDE |       |
                                  +-------+       |       |
        CPU在執(zhí)行除法指令 ---> -->| A->0  |~~~~   |       |
        的同時(shí), 預(yù)取A             +-------+   ~   |       |
                                  :       :   ~   |       |
                                  :       :DIVIDE |       |
                                  :       :   ~   |       |
                                  :       :   ~   |       |
                              rrrrrrrrrrrrrrrrr   |       |
                                  +-------+       |       |
        預(yù)取被丟棄, 并且更 --> -->| A->1  |------>|       |
        新后的值被重新獲取        +-------+       |       |
                                  :       :       +-------+




====================
內(nèi) 核中顯式的內(nèi)存屏障
====================


linux內(nèi)核擁有各式各樣的屏障, 作用在不同層次上:


  (*) 編譯優(yōu)化屏障.


  (*) CPU內(nèi)存屏障.


  (*) MMIO寫屏障.


編 譯優(yōu)化屏障
------------


Linux內(nèi)核有一個(gè)顯式的編譯器屏障函數(shù), 能夠防止編譯器優(yōu)化將訪存操作從它的任一側(cè)移
到另一側(cè):


        barrier();


這 是一個(gè)通用屏障 - 弱類型的編譯優(yōu)化屏障并不存在.


編譯優(yōu)化屏障并不直接作用到CPU, CPU依然可以按其意愿亂序執(zhí)行代碼.


(譯注: 


既然編譯優(yōu)化屏障并不能限制CPU的亂序訪存, 那么單純的編譯優(yōu)化屏障能起到什么作用呢?
以內(nèi)核中的preempt_disable宏為例: 


        #define preempt_disable() \
        do { \
                inc_preempt_count(); \
                barrier(); \
        } while (0)


preempt_disable()和對(duì)應(yīng)的preempt_enable()之間的代碼是禁止內(nèi)核搶占的, 通過對(duì)當(dāng)前
進(jìn)程的preempt_count進(jìn)行++, 以標(biāo)識(shí)進(jìn)入禁止搶占狀態(tài)(preempt_count==0時(shí)可搶占). 這
里 在對(duì)preempt_count自增之后, 使用了編譯優(yōu)化屏障.


如果不使用屏障, 本該在不可搶占狀態(tài)下執(zhí)行的指令可能被重排到preempt_count++之前(因
為這些指令基本上不會(huì)對(duì)preempt_count有依 賴). 而搶占可能是由中斷處理程序來觸發(fā)的, 
在那些應(yīng)該在不可搶占狀態(tài)下執(zhí)行的指令被執(zhí)行之后, preempt_count++之前, 可能發(fā)生中
斷. 中斷來了, preempt_count的值還是0, 于是進(jìn)程可能會(huì)被錯(cuò)誤的搶占掉.


究其原因, 是因?yàn)榫幾g器看到的上下文依賴邏輯是靜態(tài)的, 它不知道這段代碼跟中斷處理程
序還存在依賴關(guān)系, 所以沒法限制自己的亂序行為. 所以, 這里的編譯優(yōu)化屏障是必要的.


但是, 僅僅使用編譯優(yōu)化屏障就足夠了么? 是的, 因?yàn)閜reempt_count這個(gè)變量是屬于當(dāng)前
進(jìn)程的, 僅會(huì)被當(dāng)前CPU訪問.


CPU亂序可能導(dǎo)致后面應(yīng)該在禁止 搶占狀態(tài)下執(zhí)行的指令先于preempt_disable()執(zhí)行完, 但
是沒有關(guān)系, 因?yàn)榍懊嬉蔡岬竭^, CPU是"順序流入, 亂序流出"的, 就算后面的指令先執(zhí)行
完, preempt_disable()也必定已經(jīng)存在于流水線中了, CPU知道preempt_count變量將要被
修改. 而觸發(fā)搶占的代碼肯定會(huì)檢查preempt_count是否為0, 而這里的檢查又將依賴于
preempt_disable()的修改結(jié)果, 必定在preempt_disable()完成之后才會(huì)進(jìn)行.


究 其原因, 是因?yàn)镃PU看到的上下文依賴邏輯是動(dòng)態(tài)的, 它不管指令是來自于普通的處理流
程, 還是來自于中斷處理程序, 只要指令存在依賴, 它都能發(fā)現(xiàn). 所以, 對(duì)于類似這樣的只
被一個(gè)CPU所關(guān)注的內(nèi)存訪問, CPU的亂序訪存并不會(huì)存在問題. 
)


CPU 內(nèi)存屏障
-----------


Linux內(nèi)核有8種基本的CPU內(nèi)存屏障:


        類型            強(qiáng)制                    SMP環(huán)境
        =============== ======================= ===========================
        通用            mb()                    smp_mb()
        寫              wmb()                   smp_wmb()
        讀              rmb()                   smp_rmb()
        數(shù)據(jù)依賴        read_barrier_depends()  smp_read_barrier_depends()


(譯注: 這里所說有SMP屏障是只在SMP環(huán)境下才生效的屏障, 而強(qiáng)制屏障則是不管在不在SMP
環(huán)境下都生效的屏障. 這里所謂的SMP環(huán)境, 確切的說, 其實(shí)是內(nèi)核的編譯選項(xiàng)指定為SMP的
情況, 并不是指實(shí)際運(yùn)行內(nèi)核的機(jī)器的環(huán)境. 不過既然編譯選項(xiàng)指定了SMP環(huán)境, 那么編譯
生成的內(nèi)核也基本上將會(huì)運(yùn)行在SMP環(huán)境. 下面提到的UP環(huán)境亦是同理.)


除了數(shù)據(jù)依賴屏障之外, 所有的內(nèi)存屏障都隱含了編譯優(yōu)化屏障的功能. 數(shù)據(jù)依賴屏障不對(duì)
編 譯器輸出的代碼順序造成任何額外的影響.


注: 在存在數(shù)據(jù)依賴關(guān)系的情況下, 編譯器預(yù)期會(huì)將LOAD指令按正確的順序輸出(例如, 在
`a[b]`語句中, 對(duì)b的load必須放在對(duì)a[b]的load之前), 但在C規(guī)范下, 并不保證編譯器不
去預(yù)測(cè)B的值(比如預(yù)測(cè)它等于1), 于是先load a再load b(比如, 
tmp = a[1]; if (b != 1) tmp = a[b];). 編譯器在load a[b]之后又重新load b, 也可能
會(huì)存在問題, 因?yàn)閎擁有比a[b]更新的副本. 這些問題的解決尚未達(dá)成共識(shí), 然而內(nèi)核中的
ACCESS_ONCE宏是解決問題的一個(gè)好的開始.


在 UP系統(tǒng)中, SMP內(nèi)存屏障將退化成編譯器優(yōu)化屏障, 因?yàn)樗俣–PU能夠保證自身的一致性
, 并本身就能以正確的順序處理重疊的內(nèi)存訪問.


[!] 注意, SMP內(nèi)存屏障必須用于控制在SMP系統(tǒng)中的共享內(nèi)存的引用順序, 而使用鎖也能夠
    滿足需求.


強(qiáng)制屏 障不應(yīng)該用來控制SMP的影響, 因?yàn)閺?qiáng)制屏障會(huì)過多地增加UP系統(tǒng)的開銷. 不過, 在
使用MMIO來訪問松散屬性的IO內(nèi)存窗口時(shí), 強(qiáng)制屏障可以用來控制這些訪存的影響. (譯注:
這里所指的內(nèi)存窗口, 是假定對(duì)于CPU來說, 可以設(shè)置屬于不同區(qū)間的內(nèi)存地址擁有不同的
屬 性. 這些屬性可以指示一個(gè)內(nèi)存段是否可以松散訪問, 即亂序訪問.) 強(qiáng)制屏障即使在非
SMP環(huán)境下也可能需要, 因?yàn)樗鼈兛梢酝ㄟ^禁止編譯器和CPU的亂序訪存, 從而影響設(shè)備感知
到內(nèi)存操作的順序. 


還有一些更高級(jí)的屏障函數(shù):


 (*) set_mb(var, value)


     該函數(shù)將value賦值到var變量中, 然后取決于具體編譯參數(shù)下的函數(shù)實(shí)現(xiàn), 可能在之
     后插入一個(gè)內(nèi)存屏障. 在UP系統(tǒng)中, 它不能保證會(huì)插入編譯優(yōu)化屏障以外的其他屏障.


 (*) smp_mb__before_atomic_dec();
 (*) smp_mb__after_atomic_dec();
 (*) smp_mb__before_atomic_inc();
 (*) smp_mb__after_atomic_inc();


     它們跟一些進(jìn)行原子操作的函數(shù)配合使用, 這些函數(shù)進(jìn)行了原子加法, 減法, 自增和
     自減, 而又不將原子變量的值返回, 特別被用于引用計(jì)數(shù). 這些原子操作本身并不隱
     含內(nèi)存屏障. (譯注: 像這樣被操作的原子變量, 多半是孤立而沒有數(shù)據(jù)依賴的. 如果
     有數(shù)據(jù)依賴, 那么依賴關(guān)系將在一定程度上限制CPU的亂序. 否則, CPU的亂序就完全
     要靠?jī)?nèi)存屏障來限制了.)


     舉個(gè)例子, 考慮如下代碼段, 它將object標(biāo)識(shí)為已刪除, 然后將其引用計(jì)數(shù)自減:


        obj->dead = 1;
        smp_mb__before_atomic_dec();
        atomic_dec(&obj->ref_count);


     這樣可以確保設(shè)置刪除標(biāo)記在自減引用計(jì)數(shù)之前生效.


     更多信息請(qǐng)參閱Documentation/atomic_ops.txt. 想知道什么地方需要用到這些函數(shù),
     參閱"原子操作"章節(jié).


 (*) smp_mb__before_clear_bit(void);
 (*) smp_mb__after_clear_bit(void);


     它們的用途類似于原子加減的屏障. 它們通常是跟一些進(jìn)行按位解鎖操作的函數(shù)配合
     使用, 必須小心, 因?yàn)槲徊僮鞅旧硪膊⒉浑[含內(nèi)存屏障.


     考慮這樣一個(gè)場(chǎng)景, 程序通過清除鎖定位來實(shí)施一些解鎖性質(zhì)的操作. clear_bit()函
     數(shù)需要像這樣的屏障:


        smp_mb__before_clear_bit();
        clear_bit( ... );


     這樣可以防止應(yīng)該在鎖定位被清除之前發(fā)生的內(nèi)存操作漏到位清除之后去(譯注: 注意
     UNLOCK的屏障作用就是要保證它之前的訪存操作一定先于它而完成). 關(guān)于UNLOCK操作
     的實(shí)現(xiàn), 請(qǐng)參閱"鎖相關(guān)函數(shù)"章節(jié).


     更多信息請(qǐng)參閱Documentation/atomic_ops.txt. 想知道什么地方需要用到這些函
     數(shù), 參閱"原子操作"章節(jié).


MMIO寫屏障
----------


對(duì)于內(nèi)存映射IO的寫操作, Linux內(nèi)核還有一個(gè)特別的屏障:


        mmiowb();


這是一個(gè)強(qiáng)制寫屏障的變體, 能夠?qū)⑷跤行虻腎O內(nèi)存窗口變成部分有序. 它的作用可能超出
CPU與硬件的界面, 從而影響到許多層次上的硬件設(shè)備.


更 多信息請(qǐng)參閱"鎖與IO訪問"章節(jié).




====================
內(nèi)核中隱式的內(nèi)存屏障
====================


Linux 內(nèi)核中有一些其他的方法也隱含了內(nèi)存屏障, 包括鎖和調(diào)度方法.


這個(gè)范圍是一個(gè)最低限度的保證; 一些特定的體系結(jié)構(gòu)可能提供更多的保證, 但是在特定體
系結(jié)構(gòu)的代碼之外, 不能依賴于它們.


鎖相關(guān)函數(shù)
----------


Linux 內(nèi)核有很多鎖結(jié)構(gòu):


 (*) spin locks
 (*) R/W spin locks
 (*) mutexes
 (*) semaphores
 (*) R/W semaphores
 (*) RCU


在 所有情況下, 它們都是LOCK操作和UNLOCK操作的變種. 這些操作都隱含一定的屏障:


 (1) LOCK操作所隱含的:


     在LOCK操作之后出現(xiàn)的內(nèi)存操作, 一定在LOCK操作完成之后才會(huì)完成.


     而在LOCK操作之前出現(xiàn)的內(nèi)存操作, 可能在LOCK操作完成之后才完成.


 (2) UNLOCK操作所隱含的:


     在UNLOCK操作之前出現(xiàn)的內(nèi)存操作, 一定在UNLOCK操作完成之前完成.


     而在UNLOCK操作之后出現(xiàn)的內(nèi)存操作, 可能在LOCK操作完成之前就完成了.


 (3) LOCK操作+LOCK操作所隱含的:


     在某個(gè)LOCK操作之前出現(xiàn)的所有LOCK操作都將在這個(gè)LOCK之前完成.


 (4) LOCK操作+UNLOCK操作所隱含的:


     在UNLOCK操作之前出現(xiàn)的所有LOCK操作都將在這個(gè)UNLOCK之前完成.


     在LOCK操作之前出現(xiàn)的所有UNLOCK操作都將在這個(gè)LOCK之前完成.


 (5) LOCK失敗所隱含的:


     某些變種的LOCK操作可能會(huì)失敗, 比如可能因?yàn)椴荒芰⒖太@得鎖(譯注: 如try_lock操
     作), 再比如因?yàn)樵谒叩却i變?yōu)榭捎玫倪^程中接收到了未被阻塞的信號(hào)(譯注: 如
     semaphores的down_interruptible操作). 失敗的鎖操作不隱含任何屏障.


因此, 根據(jù)(1), (2)和(4), 一個(gè)無條件的LOCK跟在一個(gè)UNLOCK之后, 鎖相當(dāng)于一個(gè)完整的
屏障, 而一個(gè)UNLOCK跟在一個(gè)LOCK之后并非如此.


[!] 注意: LOCK和UNLOCK只是單向的屏障, 其結(jié)果是, 臨界區(qū)之外的指令可能會(huì)在臨界區(qū)中
    執(zhí)行.


一個(gè)UNLOCK跟在一個(gè)LOCK之后并不能認(rèn)為是一個(gè)完整的屏障, 因?yàn)槌霈F(xiàn)在LOCK之前的訪存可
能在LOCK之后才執(zhí)行, 而出現(xiàn)在UNLOCK之后的訪存可能在UNLOCK之前執(zhí)行, 這兩次訪存可能
會(huì) 交叉:


        *A = a;
        LOCK
        UNLOCK
        *B = b;


可能表現(xiàn)為:


        LOCK, STORE *B, STORE *A, UNLOCK


鎖和信號(hào)量在UP環(huán)境下可能不提供順序保證, 在這種情況下不能被認(rèn)作是真正的屏障 - 特
別是對(duì) 于IO訪問 - 除非結(jié)合中斷禁用操作.


參閱"跨CPU的鎖的屏障作用"章節(jié).


例如, 考慮如下代碼:


        *A = a;
        *B = b;
        LOCK
        *C = c;
        *D = d;
        UNLOCK
        *E = e;
        *F = f;


如 下的事件序列都是可接受的:


        LOCK, {*F,*A}, *E, {*C,*D}, *B, UNLOCK


        [+] 注意, {*F,*A} 代表一次合并訪問.


但是下面的序列都不可接受:


        {*F,*A}, *B,    LOCK, *C, *D,    UNLOCK, *E
        *A, *B, *C,     LOCK, *D,        UNLOCK, *E, *F
        *A, *B,         LOCK, *C,        UNLOCK, *D, *E, *F
        *B,             LOCK, *C, *D,    UNLOCK, {*F,*A}, *E


禁止中斷函數(shù)
------------


禁止中斷(類 似于LOCK)和啟用中斷(類似于UNLOCK)的函數(shù)只會(huì)起到編譯優(yōu)化屏障的作用. 所
以, 如果在這種情況下需要使用內(nèi)存或IO屏障, 必須采取其他手段.


睡眠喚醒函數(shù)
------------


在一個(gè)全局事件標(biāo)記上的睡眠和喚醒可 以被看作是兩條數(shù)據(jù)之間的交互: 正在等待事件的進(jìn)
程的狀態(tài), 和用于表示事件發(fā)生的全局?jǐn)?shù)據(jù). 為了確保它們按正確的順序發(fā)生, 進(jìn)入睡眠的
原 語和發(fā)起喚醒的原語都隱含了某些屏障.


首先, 睡眠進(jìn)程通常執(zhí)行類似于如下的代碼序列:


        for (;;) {
                set_current_state(TASK_UNINTERRUPTIBLE);
                if (event_indicated)
                        break;
                schedule();
        }


set_current_state()在它更 改進(jìn)程狀態(tài)之后會(huì)自動(dòng)插入一個(gè)通用內(nèi)存屏障:


        CPU 1
        ===============================
        set_current_state();
          set_mb();
            STORE current->state
            <通用屏障>
        LOAD event_indicated


set_current_state() 可能被包裝在以下函數(shù)中:


        prepare_to_wait();
        prepare_to_wait_exclusive();


因此這些函數(shù)也隱含了一個(gè)在設(shè)置了進(jìn)程狀態(tài)之后的通用內(nèi)存屏障. 以上的各個(gè)函數(shù)又被包
裝在其他一些函數(shù)中, 所有這些包裝函數(shù)都相當(dāng)于在對(duì)應(yīng)的位置插入了內(nèi)存屏障:


        wait_event();
        wait_event_interruptible();
        wait_event_interruptible_exclusive();
        wait_event_interruptible_timeout();
        wait_event_killable();
        wait_event_timeout();
        wait_on_bit();
        wait_on_bit_lock();


其次, 用作喚醒操作的代碼通常是下面這樣:


        event_indicated = 1;
        wake_up(&event_wait_queue);


或:


        event_indicated = 1;
        wake_up_process(event_daemon);


類 似wake_up()的函數(shù)會(huì)隱含一個(gè)寫內(nèi)存屏障. 當(dāng)且僅當(dāng)它們的確喚醒了某個(gè)進(jìn)程時(shí). 屏障
出現(xiàn)在進(jìn)程的睡眠狀態(tài)被清除之前, 也就是在設(shè)置喚醒事件標(biāo)記的STORE操作和將進(jìn)程狀態(tài)
修改為TASK_RUNNING的STORE操作之間:


        CPU 1                           CPU 2
        =============================== ===============================
        set_current_state();            STORE event_indicated
          set_mb();                       wake_up();
            STORE current->state            <寫屏障>
            <通用屏障>                      STORE current->state
        LOAD event_indicated


可用的喚醒函數(shù)包括:


        complete();
        wake_up();
        wake_up_all();
        wake_up_bit();
        wake_up_interruptible();
        wake_up_interruptible_all();
        wake_up_interruptible_nr();
        wake_up_interruptible_poll();
        wake_up_interruptible_sync();
        wake_up_interruptible_sync_poll();
        wake_up_locked();
        wake_up_locked_poll();
        wake_up_nr();
        wake_up_poll();
        wake_up_process();


[!] 注意, 對(duì)于喚醒函數(shù)讀寫事件之前, 睡眠函數(shù)調(diào)用set_current_state()之后的那些
    STORE操作, 睡眠和喚醒所隱含的內(nèi)存屏障并不保證它們的順序. 比如說, 如果睡眠
    函數(shù)這樣做:


        set_current_state(TASK_INTERRUPTIBLE);
        if (event_indicated)
                break;
        __set_current_state(TASK_RUNNING);
        do_something(my_data);


而喚醒函數(shù)這樣做:


        my_data = value;
        event_indicated = 1;
        wake_up(&event_wait_queue);


睡眠函數(shù)并不能保證在看到my_data的修改之后才看到 event_indicated的修改. 在這種情況
下, 兩邊的代碼必須在對(duì)my_data訪存之前插入自己的內(nèi)存屏障. 因此上述的睡眠函數(shù)應(yīng)該
這樣做:


        set_current_state(TASK_INTERRUPTIBLE);
        if (event_indicated) {
                smp_rmb();
                do_something(my_data);
        }


而喚醒函數(shù)應(yīng)該這樣做:


        my_data = value;
        smp_wmb();
        event_indicated = 1;
        wake_up(&event_wait_queue);


其他函數(shù)
--------


其 他隱含了屏障的函數(shù):


 (*) schedule()和類似函數(shù)隱含了完整的內(nèi)存屏障.
     (譯注: schedule函數(shù)完成了進(jìn)程的切換, 它的兩邊可能對(duì)應(yīng)著兩個(gè)不同的上下文. 如
     果訪存操作跨越schedule函數(shù)而進(jìn)行了亂序, 那么基本上可以肯定是錯(cuò)誤的.)




===================
跨 CPU的鎖的屏障作用
===================


在SMP系統(tǒng)中, 鎖定原語給出了多種形式的屏障: 其中一種在一些特定的鎖沖突的情況下, 
會(huì)影響其他CPU上的內(nèi)存訪問順序. 


鎖與內(nèi)存訪問
------------


假 設(shè)系統(tǒng)中有(M)和(Q)這一對(duì)spinlock, 有三個(gè)CPU; 那么可能發(fā)生如下操作序列:


        CPU 1                           CPU 2
        =============================== ===============================
        *A = a;                         *E = e;
        LOCK M                          LOCK Q
        *B = b;                         *F = f;
        *C = c;                         *G = g;
        UNLOCK M                        UNLOCK Q
        *D = d;                         *H = h;


那么對(duì)于CPU 3來說, 從*A到*H的訪問順序是沒有保證的, 不像單獨(dú)的鎖對(duì)應(yīng)單獨(dú)的CPU有
那樣的限制. 例如, CPU 3可能看到的順序是:


        *E, LOCK M, LOCK Q, *G, *C, *F, *A, *B, UNLOCK Q, *D, *H, UNLOCK M


但是它不會(huì)看到如下情況:


        *B, *C or *D 先于 LOCK M
        *A, *B or *C 后于 UNLOCK M
        *F, *G or *H 先于 LOCK Q
        *E, *F or *G 后于 UNLOCK Q


但 是, 如果是下面的情形:


        CPU 1                           CPU 2
        =============================== ===============================
        *A = a;
        LOCK M          [1]
        *B = b;
        *C = c;
        UNLOCK M        [1]
        *D = d;                         *E = e;
                                        LOCK M          [2]
                                        *F = f;
                                        *G = g;
                                        UNLOCK M        [2]
                                        *H = h;


CPU 3可能看到:


        *E, LOCK M [1], *C, *B, *A, UNLOCK M [1],
                LOCK M [2], *H, *F, *G, UNLOCK M [2], *D


但是如果CPU 1先得到鎖, CPU 3不會(huì)看到下面的情況:


        *B, *C, *D, *F, *G or *H 先于 LOCK M [1]
        *A, *B or *C 后于 UNLOCK M [1]
        *F, *G or *H 先于 LOCK M [2]
        *A, *B, *C, *E, *F or *G 后于 UNLOCK M [2]


鎖與IO訪問
----------


在 某些情況下(特別是涉及到NUMA的情況), 兩個(gè)CPU上發(fā)起的屬于兩個(gè)spinlock臨界區(qū)的IO
訪問可能被PCI橋看成是交錯(cuò)發(fā)生的, 因?yàn)镻CI橋并不一定參與cache一致性協(xié)議, 以至于無
法響應(yīng)讀內(nèi)存屏障.


例如:


        CPU 1                                CPU 2
        ===============================      ===============================
        spin_lock(Q)
        writel(0, ADDR)
        writel(1, DATA);
        spin_unlock(Q);
                                             spin_lock(Q);
                                             writel(4, ADDR);
                                             writel(5, DATA);
                                             spin_unlock(Q);


PCI橋可能看到的是:


        STORE *ADDR = 0, STORE *ADDR = 4, STORE *DATA = 1, STORE *DATA = 5


這可能會(huì)引起硬件 操作的錯(cuò)誤.


這里所需要的是, 在釋放spinlock之前, 使用mmiowb()作為干預(yù), 例如:


        CPU 1                                CPU 2
        ===============================      ===============================
        spin_lock(Q)
        writel(0, ADDR)
        writel(1, DATA);
        mmiowb();
        spin_unlock(Q);
                                             spin_lock(Q);
                                             writel(4, ADDR);
                                             writel(5, DATA);
                                             mmiowb();
                                             spin_unlock(Q);


這樣就能確保CPU 1的兩次STORE操作先于CPU 2的STORE操作被PCI橋所看到.


此 外, 對(duì)于同一硬件設(shè)備在進(jìn)行STORE操作之后再進(jìn)行LOAD操作, 可以省去mmiowb(), 因?yàn)?br>LOAD操作將強(qiáng)制STORE操作在開 始LOAD之前就完成:


        CPU 1                                CPU 2
        ===============================      ===============================
        spin_lock(Q)
        writel(0, ADDR)
        a = readl(DATA);
        spin_unlock(Q);
                                             spin_lock(Q);
                                             writel(4, ADDR);
                                             b = readl(DATA);
                                             spin_unlock(Q);


更多信息請(qǐng)參 閱"Documentation/DocBook/deviceiobook.tmpl".




=====================
什 么地方需要內(nèi)存屏障?
=====================


在正常操作下, 內(nèi)存操作的亂序一般并不會(huì)成為問題, 即使是在SMP內(nèi)核中, 一段單線程的
線性代碼也總是能夠正確工作. 但是, 有四種情況, 亂序絕對(duì)可能是一個(gè)問題:


 (*) 處理器間交互.


 (*) 原子操作.


 (*) 訪問設(shè)備.


 (*) 中斷.


處 理器間交互
------------


當(dāng)系統(tǒng)中擁有不止一個(gè)CPU時(shí), 系統(tǒng)中的多個(gè)CPU可能在同一時(shí)間工作在同樣的數(shù)據(jù)集上. 這
將產(chǎn)生同步問題, 并且這樣的問題通常要靠使用鎖來解決. 但是, 鎖是昂貴的, 所以不是萬
不得已的情況下最好不要使用鎖. 在這種情況下, 為防止錯(cuò)誤, 導(dǎo)致兩個(gè)CPU相互影響的那
些內(nèi)存操作可能需要仔細(xì)協(xié)調(diào)好順 序.


比如, 考慮一下讀寫信號(hào)量的slow path. 信號(hào)量的等待隊(duì)列里有一個(gè)進(jìn)程正在等待, 這個(gè)
等待進(jìn)程??臻g 上的一段內(nèi)存(譯注: 也就是棧上分配的waiter結(jié)構(gòu))被鏈到信號(hào)量的等待鏈
表里:


        struct rw_semaphore {
                ...
                spinlock_t lock;
                struct list_head waiters;
        };


        struct rwsem_waiter {
                struct list_head list;
                struct task_struct *task;
        };


要喚醒這樣一個(gè) 等待進(jìn)程, up_read()函數(shù)或up_write()函數(shù)需要這樣做:


 (1) 讀取該等待進(jìn)程所對(duì)應(yīng)的waiter結(jié)構(gòu)的next指針, 以記錄下一個(gè)等待進(jìn)程是誰;


 (2) 讀取waiter結(jié)構(gòu)中的task指針, 以獲取對(duì)應(yīng)進(jìn)程的進(jìn)程控制塊;


 (3) 清空waiter結(jié)構(gòu)中的task指針, 以表示這個(gè)進(jìn)程正在獲得信號(hào)量;


 (4) 對(duì)這個(gè)進(jìn)程調(diào)用wake_up_process()函數(shù); 并且


 (5) 釋放waiter結(jié)構(gòu)對(duì)進(jìn)程控制塊的引用計(jì)數(shù).


換句話說, 這個(gè)過程會(huì)執(zhí)行如下事件序列:


        LOAD waiter->list.next;
        LOAD waiter->task;
        STORE waiter->task;
        CALL wakeup
        RELEASE task


而 如果其中一些步驟發(fā)生了亂序, 那么整個(gè)過程可能會(huì)產(chǎn)生錯(cuò)誤.


一旦等待進(jìn)程將自己掛入等待隊(duì)列, 并釋放了信號(hào)量里的鎖, 這個(gè)等待進(jìn)程就不會(huì)再獲得這
個(gè)鎖了(譯注: 參閱信號(hào)量的代碼, 它內(nèi)部使用了一個(gè)spinlock來進(jìn)行同步); 它要做的事情
就是在 繼續(xù)工作之前, 等待waiter結(jié)構(gòu)中的task指針被清空(譯注: 然后自己會(huì)被喚醒). 而
既然waiter結(jié)構(gòu)存在于等待進(jìn)程的棧上, 這就意味著, 如果在waiter結(jié)構(gòu)中的next指針被讀
取之前, task指針先被清空了的話(譯注: 等待進(jìn)程先被喚醒了), 那么, 這個(gè)等待進(jìn)程可能
已經(jīng)在另一個(gè)CPU上開始運(yùn)行了(譯注: 相對(duì)于喚醒進(jìn)程所運(yùn)行的CPU), 并且在up*()函數(shù)有
機(jī)會(huì)讀取到 next指針之前, 棧空間上對(duì)應(yīng)的waiter結(jié)構(gòu)可能已經(jīng)被復(fù)用了(譯注: 被喚醒的
進(jìn)程從down*()函數(shù)返回, 然后可能進(jìn)行新的函數(shù)調(diào)用, 導(dǎo)致??臻g被重復(fù)使用).


看看上面的事件序列可能會(huì)發(fā)生什么:


        CPU 1                           CPU 2
        =============================== ===============================
                                        down_xxx()
                                        將waiter結(jié)構(gòu)鏈入等待隊(duì)列
                                        進(jìn)入睡眠
        up_yyy()
        LOAD waiter->task;
        STORE waiter->task;
                                        被CPU 1的UP事件喚醒
        <被搶占了>
                                        重新得到運(yùn)行
                                        down_xxx()函數(shù)返回
                                        繼續(xù)調(diào)用foo()函數(shù)
                                        foo()重用了棧上的waiter結(jié)構(gòu)
        <搶占返回>
        LOAD waiter->list.next;
        --- OOPS ---


對(duì)付這個(gè)問題可以 使用信號(hào)量中的鎖, 但是當(dāng)進(jìn)程被喚醒后, down_xxx()函數(shù)其實(shí)沒必要重
新獲得這個(gè)spinlock.


實(shí)際的 解決辦法是插入一個(gè)通用SMP內(nèi)存屏障:


        LOAD waiter->list.next;
        LOAD waiter->task;
        smp_mb();
        STORE waiter->task;
        CALL wakeup
        RELEASE task


這 樣, 對(duì)于系統(tǒng)中的其他CPU來說, 屏障將保證屏障之前的所有內(nèi)存訪問先于屏障之后的所
有內(nèi)存訪問發(fā)生. 屏障并不保證屏障之前的所有內(nèi)存訪問都在屏障指令結(jié)束之前完成.


在UP系統(tǒng)中 - 這種情況將不是問題 - smp_mb()函數(shù)只是一個(gè)編譯優(yōu)化屏障, 這就確保了編
譯器生成順序正確的指令, 而不需要干預(yù)CPU. 既然只有一個(gè)CPU, 該CPU的數(shù)據(jù)依賴邏輯將
處理所有事情.


原子操作
--------


雖然原子操作在技術(shù) 上實(shí)現(xiàn)了處理器之間的交互, 然而特別注意一些原子操作隱含了完整的
內(nèi)存屏障, 而另外一些則沒有, 但是它們卻作為一個(gè)群體被整個(gè)內(nèi)核嚴(yán)重依賴. 


許多原子操作修改內(nèi)存中的一些狀態(tài), 并且返回該狀態(tài)相關(guān)的信息(舊狀態(tài)或新狀態(tài)), 就在
其中實(shí)際操作內(nèi)存的兩邊各隱含一個(gè)SMP環(huán)境下的通用內(nèi)存屏障(smp_mb())(除顯式 的鎖操作
之外, 稍后說明). 它們包括:


        xchg();
        cmpxchg();
        atomic_cmpxchg();
        atomic_inc_return();
        atomic_dec_return();
        atomic_add_return();
        atomic_sub_return();
        atomic_inc_and_test();
        atomic_dec_and_test();
        atomic_sub_and_test();
        atomic_add_negative();
        atomic_add_unless();        /* 如果成功 (返回 1) */
        test_and_set_bit();
        test_and_clear_bit();
        test_and_change_bit();


它們被用 于作為類LOCK和類UNLOCK操作的實(shí)現(xiàn), 和用于控制對(duì)象析構(gòu)的引用計(jì)數(shù), 這些情況
下, 隱含內(nèi)存屏障是有必要的.


以 下操作由于沒有隱含內(nèi)存屏障, 會(huì)有潛在的問題, 但有可能被用于實(shí)現(xiàn)類UNLOCK這樣的操
作:


        atomic_set();
        set_bit();
        clear_bit();
        change_bit();


如果需要, 對(duì)應(yīng)于這些函數(shù), 可以使用相應(yīng)的顯式內(nèi)存屏障(比如
smp_mb__before_clear_bit()).


下 面這些函數(shù)也不隱含內(nèi)存屏障, 并且在一些情況下, 可能也需要用到顯式內(nèi)存屏障(比如
smp_mb__before_atomic_dec()):


        atomic_add();
        atomic_sub();
        atomic_inc();
        atomic_dec();


如果它們用于產(chǎn)生統(tǒng)計(jì), 那么他們可能就不需要內(nèi)存屏障, 除非統(tǒng)計(jì)數(shù)據(jù)之間存在耦合.


如 果它們被用作控制對(duì)象生命周期的引用計(jì)數(shù), 那么它們可能并不需要內(nèi)存屏障, 因?yàn)橐?br>引用計(jì)數(shù)需要在一個(gè)鎖的臨界區(qū)里面進(jìn)行調(diào)整, 要么調(diào)用者已經(jīng)持有足夠的引用而相當(dāng)于擁
有了鎖(譯注: 一般在引用計(jì)數(shù)減為0的時(shí)候需要將對(duì)應(yīng)的對(duì)象析構(gòu), 如果調(diào)用者知道引用計(jì)
數(shù)在 某些情況下不可能減為0, 那么這個(gè)對(duì)象也就不可能在這些情況下被析構(gòu), 也就不需要
通過內(nèi)存屏障來避免訪存亂序?qū)е碌膶?duì)象在析構(gòu)之后還被訪問的 情況), 這樣的情況下并不
需要內(nèi)存屏障.


如果它們用于構(gòu)成鎖的一些描述信息, 那么他們可能就需要內(nèi)存屏障, 因?yàn)殒i原語一般需要
按一定的順序來操作.


基本上, 每個(gè)場(chǎng)景都需要仔細(xì)考慮是否需要使用內(nèi)存屏障.


以 下操作是特殊的鎖原語:


        test_and_set_bit_lock();
        clear_bit_unlock();
        __clear_bit_unlock();


它們都執(zhí)行了類 LOCK和類UNLOCK的操作. 相比其他操作, 它們應(yīng)該優(yōu)先被用于實(shí)現(xiàn)鎖原語, 
因?yàn)樗鼈兊膶?shí)現(xiàn)可以在許多體系結(jié)構(gòu)下得到優(yōu)化.


[!] 注意, 這些特殊的內(nèi)存屏障原語對(duì)一些情況也是有用的, 因?yàn)樵谝恍w系結(jié)構(gòu)的CPU上,
    使用的原子操作本身就隱含了完整的內(nèi)存屏障功能, 所以屏障指令在這里是多余的, 在
    這樣的情況下, 這些特殊的屏障原語將不使用額外的屏障操作.


更多信息請(qǐng)參閱Documentation/atomic_ops.txt.


訪 問設(shè)備
--------


許多設(shè)備都可以被映射到內(nèi)存, 因此在CPU看來, 它們只是一組內(nèi)存地址. 為了控制這些設(shè)
備, 驅(qū)動(dòng)程序通常需要確保正確的內(nèi)存訪問按正確的順序來執(zhí)行.


但是, 聰明的CPU或者聰明的編譯器卻導(dǎo)致了潛在的問題, 如果CPU或編譯器認(rèn)為亂序, 或合
并訪問更有利于效率的話, 驅(qū)動(dòng)程序代碼中仔細(xì)安排的訪存序列可能并不會(huì)按正確的順序被
送到設(shè)備上 - 從而可能導(dǎo)致設(shè)備的錯(cuò)誤.


在Linux內(nèi)核里面, IO訪問應(yīng)該使用適當(dāng)?shù)脑L問函數(shù) - 例如inb()或writel() - 它們知道如
何得到恰當(dāng)?shù)脑L問順序. 大多數(shù)情況下, 在使用這些函數(shù)之后就不必再顯式的使用內(nèi)存屏障
, 但是在兩種情況下, 內(nèi)存屏障可能還是需要的:


 (1) 在一些系統(tǒng)中, IO存儲(chǔ)操作對(duì)于所有CPU來說并不是嚴(yán)格有序的, 所以對(duì)于所有的通用
     驅(qū)動(dòng)程序(譯注: 通用驅(qū)動(dòng)程序需要適應(yīng)各種體系結(jié)構(gòu)的系統(tǒng)), 需要使用鎖, 并且一
     定要在解鎖臨界區(qū)之前執(zhí)行mmiowb()函數(shù).


 (2) 如果訪存函數(shù)訪問松散屬性的IO內(nèi)存窗口, 那么需要使用強(qiáng)制內(nèi)存屏障來確保執(zhí)行順
     序.


更多信息請(qǐng)參閱Documentation/DocBook /deviceiobook.tmpl.


中斷
----


驅(qū)動(dòng)程序可能被它自己的中斷處理程序所打 斷, 然后驅(qū)動(dòng)程序中的這兩個(gè)部分可能會(huì)相互干
擾對(duì)方控制或訪問設(shè)備的意圖.


通過禁用本地中斷(一種形式的鎖)可能至少 部分緩解這種情況, 這樣的話, 驅(qū)動(dòng)程序中的關(guān)
鍵操作都將包含在禁用中斷的區(qū)間中. 于是當(dāng)驅(qū)動(dòng)程序的中斷處理程序正在執(zhí)行時(shí), 驅(qū)動(dòng)程
序 的核心代碼不可能在相同的CPU上運(yùn)行, 并且在當(dāng)前中斷被處理完之前中斷處理程序不允
許再次被調(diào)用, 于是中斷處理程序就不需要再對(duì)這種情況使用鎖.


但是, 考慮一個(gè)驅(qū)動(dòng)程序正通過一個(gè)地址寄存器和一個(gè)數(shù)據(jù)寄存器跟以太網(wǎng)卡交互的情況.
假設(shè)驅(qū)動(dòng)程序的核心代碼在禁用中斷的情況下操作了網(wǎng)卡, 然后驅(qū)動(dòng)程序的中斷處理程序
被調(diào)用:


        LOCAL IRQ DISABLE
        writew(ADDR, 3);
        writew(DATA, y);
        LOCAL IRQ ENABLE
        <進(jìn)入中斷>
        writew(ADDR, 4);
        q = readw(DATA);
        <退出中斷>


如果執(zhí)行順序的規(guī)則足夠松散, 對(duì)數(shù)據(jù)寄存器的寫操作可能發(fā)生在第二次對(duì)地址寄存器的寫
操作之后:


        STORE *ADDR = 3, STORE *ADDR = 4, STORE *DATA = y, q = LOAD *DATA


如果執(zhí)行順序像這樣松散, 就需要假定在禁用中斷區(qū)間內(nèi)應(yīng)該完成的訪問可能泄漏到區(qū)間之
外, 并且可能漏到中斷過程中進(jìn)行訪問 - 反之亦然 - 除非使用隱式或顯式的屏障.


通常這并不是一個(gè)問題, 因?yàn)榻弥袛鄥^(qū)間內(nèi)完成的IO訪存將會(huì)包含嚴(yán)格有序的同步LOAD操
作, 形成隱式的IO屏障. 如果這還不夠, 那么需要顯式的調(diào)用一下mmiowb().


在一個(gè)中斷服務(wù)程序與兩個(gè)運(yùn)行在不同CPU 的程序相互通信的情況下, 類似的情況也可能發(fā)
生. 如果出現(xiàn)這樣的情況, 那么禁用中斷的鎖操作需要用于確保執(zhí)行順序. (譯注: 也就是
類 似于spinlock_irq這樣的操作.)




===================
內(nèi)核中I/O屏障 的作用
===================


在對(duì)IO內(nèi)存進(jìn)行存取的時(shí)候, 驅(qū)動(dòng)程序應(yīng)該使用適當(dāng)?shù)拇嫒『瘮?shù):


 (*) inX(), outX():


     它們都是傾向于跟IO空間打交道, 而不是普通內(nèi)存空間, 不過這主要取決于具體CPU的
     邏輯. i386和x86_64處理器確實(shí)有特殊的IO空間存取周期和指令, 但是許多系統(tǒng)結(jié)構(gòu)
     的CPU卻并沒有這些概念.


     包括PCI總線也可能會(huì)定義成IO空間 - 比如在i386和x86_64的CPU上 - 很容易將它映
     射到CPU的IO空間上. 但是, 它也可能作為虛擬的IO空間被映射到CPU的內(nèi)存空間上, 
     特別對(duì)于那些不支持IO空間的CPU.


     訪問這些空間可能是完全同步的(比如在i386上), 但是對(duì)于橋設(shè)備(比如PCI主橋)可能
     并不完全是這樣.


     他們能保證完全遵守IO操作之間的訪問順序.


     他們不能保證完全遵從IO操作與其他類型的內(nèi)存操作之間的訪問順序.


 (*) readX(), writeX():


     在發(fā)起調(diào)用的CPU上, 這些函數(shù)是否保證完全遵從內(nèi)存訪問順序而且不進(jìn)行合并訪問,
     取決于它們所訪問的內(nèi)存窗口上定義的屬性. 例如, 較新的i386體系結(jié)構(gòu)的機(jī)器, 可
     以通過MTRR寄存器來控制內(nèi)存窗口的屬性.


     通常, 只要不是訪問預(yù)取設(shè)備, 這些函數(shù)將保證完全有序并且不進(jìn)行合并訪問.


     但是對(duì)于橋設(shè)備(比如PCI橋), 如果它們?cè)敢獾脑? 可能會(huì)傾向于對(duì)內(nèi)存操作進(jìn)行延遲
     處理; 要沖刷一個(gè)STORE操作, 首選是對(duì)相同地址進(jìn)行一次LOAD[*], 但是對(duì)于PCI來說
     , 對(duì)相同設(shè)備或相同的配置的IO空間進(jìn)行一次LOAD就足夠了.


     [*] 注意! 試圖從剛寫過的地址LOAD數(shù)據(jù), 可能會(huì)導(dǎo)致錯(cuò)誤 - 比如對(duì)于16550 Rx/Tx
         串口寄存器.


     遇到帶預(yù)取的IO內(nèi)存, 可能需要使用mmiowb()屏障來強(qiáng)制讓STORE操作有序.


     關(guān)于PCI事務(wù)交互方面的更多信息, 請(qǐng)參閱PCI規(guī)范.


 (*) readX_relaxed()


     這些函數(shù)類似于readX(), 但是任何情況下都不保證有序. 請(qǐng)注意, 這里沒有用到IO讀
     屏障.


 (*) ioreadX(), iowriteX()


     這些函數(shù)在進(jìn)行訪存的時(shí)候會(huì)根據(jù)訪存類型選擇適當(dāng)?shù)牟僮? inX()/outX()或
     readX()/writeX().




======================
最 小限度有序的假想模型
======================


從概念上說, 必須假定的CPU是弱有序的, 但是它會(huì)保持程序上下文邏輯關(guān)系的外觀. 一些
CPU(比如i386或x86_64)比另一些(比如powerpc或frv)更具有約束力, 而在體系結(jié)構(gòu)無關(guān)的
代碼中, 必須假定為最松散的情況(也就是DEC Alpha).


也就是說, 必須考慮到CPU可能會(huì)按它喜歡的順序來執(zhí)行操作 - 甚至并行執(zhí)行 - 只是當(dāng)指
令流中的一條指令依賴于之前的一條指令時(shí), 之前的這條指定才必須在后面這條指令可能被
處理之前完全結(jié)束; 換句話說: 保持程序的上下文邏輯關(guān)系.


 [*] 一些指令會(huì)產(chǎn)生不止一處影響 - 比如會(huì)修改條件碼, 修改寄存器或修改內(nèi)存 - 不同
     的指令可能依賴于不同的影響.


CPU 也可能丟棄那些最終不產(chǎn)生任何影響的操作序列. 比如, 如果兩個(gè)相鄰的指令都將一個(gè)
立即數(shù)LOAD到寄存器, 那么第一個(gè)LOAD指令可能被丟棄.


類似的, 也需要假設(shè)編譯器可能按它覺得舒服的順序來調(diào)整指令流, 但同樣也會(huì)保持程序的
上 下文邏輯關(guān)系.




===============
CPU cache的影響
===============


操 作cache中緩存的內(nèi)存之后, 相應(yīng)的影響會(huì)在整個(gè)系統(tǒng)間得到傳播. 位于CPU和內(nèi)存之間的
cache, 和保持系統(tǒng)狀態(tài)一致的內(nèi)存一致性機(jī)構(gòu), 在一定程度上影響了傳播的方法.


自從CPU與系統(tǒng)中其他部分的交互通過使用cache來 實(shí)現(xiàn)以來, 內(nèi)存系統(tǒng)就包含了CPU的緩存,
而內(nèi)存屏障基本上就工作在CPU和其cache之間的界面上(邏輯上說, 內(nèi)存屏障工作在下圖中
虛 線所示的地方):


            <--- CPU --->         :       <----------- 內(nèi)存 ----------->
                                  :
        +--------+    +--------+  :   +--------+    +-----------+
        |        |    |        |  :   |        |    |           |    +--------+
        |  CPU   |    |  內(nèi)存  |  :   | CPU    |    |           |    |        |
        |  核心  |--->|  請(qǐng)求  |----->| Cache  |<-->|           |    |        |
        |        |    |  隊(duì)列  |  :   |        |    |           |--->|  內(nèi)存  |
        |        |    |        |  :   |        |    |           |    |        |
        +--------+    +--------+  :   +--------+    |           |    |        |
                                  :                 |  Cache    |    +--------+
                                  :                 |  一致性   |
                                  :                 |  機(jī)構(gòu)     |    +--------+
        +--------+    +--------+  :   +--------+    |           |    |        |
        |        |    |        |  :   |        |    |           |    |        |
        |  CPU   |    |  內(nèi)存  |  :   | CPU    |    |           |--->|  設(shè)備  |
        |  核心  |--->|  請(qǐng)求  |----->| Cache  |<-->|           |    |        |
        |        |    |  隊(duì)列  |  :   |        |    |           |    |        |
        |        |    |        |  :   |        |    |           |    +--------+
        +--------+    +--------+  :   +--------+    +-----------+
                                  :
                                  :


一些LOAD和STORE可能不會(huì)實(shí)際出現(xiàn)在發(fā)起操作的 CPU之外, 因?yàn)樵贑PU自己的cache上就能滿
足需要, 盡管如此, 如果其他CPU關(guān)心這些數(shù)據(jù), 那么完整的內(nèi)存訪問還是會(huì)發(fā)生, 因?yàn)?br>cache一致性機(jī)構(gòu)將遷移相應(yīng)的cache行到訪問它的CPU, 使一致性得到傳播.


在保持程序所期望的上下文邏 輯的前提下, CPU核心可能會(huì)按它認(rèn)為合適的順序來執(zhí)行指令.
一些指令會(huì)產(chǎn)生LOAD和STORE操作, 并且將它們放到內(nèi)存請(qǐng)求隊(duì)列中, 等待被執(zhí)行. CPU核心
可能會(huì)按它喜歡的順序來將這些操作放進(jìn)隊(duì)列, 然后繼續(xù)運(yùn)行, 直到它必須等待這些訪存指
令完成的時(shí)候?yàn)橹?


內(nèi) 存屏障所需要關(guān)心的是訪存操作從CPU一側(cè)穿越到內(nèi)存一側(cè)的順序, 和系統(tǒng)中的其他部件
感知到的操作發(fā)生的順序.


[!] 對(duì)于一個(gè)CPU自己的LOAD和STORE來說, 并不需要使用內(nèi)存屏障, 因?yàn)镃PU總是能按程序
    執(zhí)行順序看到它們所執(zhí)行的LOAD和STORE操作.


[!] MMIO或其他設(shè)備存取可能繞開cache系統(tǒng). 這取決于訪問設(shè)備所經(jīng)過的內(nèi)存窗口的屬性
    和/或是否使用了CPU所特有的與設(shè)備進(jìn)行交互的指令.


CACHE一致 性
-----------


但是, 事情并不是像上面所說的那樣簡(jiǎn)單: 因?yàn)殡m然可以期望cache是一致的, 但是一致性
傳播的順序卻是沒有保證的. 也就是說, 雖然一個(gè)CPU所做出的更新將最終被其它CPU都看到
, 但是卻不保證其他CPU所看到的都是相同的順序.


考慮這樣一個(gè)系統(tǒng), 它具有雙CPU(1和2), 每個(gè)CPU有一對(duì)并行的數(shù)據(jù)cache(CPU 1對(duì)應(yīng)A/B, 
CPU 2對(duì)應(yīng)C/D):


                    :
                    :                          +--------+
                    :      +---------+         |        |
        +--------+  : +--->| Cache A |<------->|        |
        |        |  : |    +---------+         |        |
        |  CPU 1 |<---+                        |        |
        |        |  : |    +---------+         |        |
        +--------+  : +--->| Cache B |<------->|        |
                    :      +---------+         |        |
                    :                          |  內(nèi)存  |
                    :      +---------+         |  系統(tǒng)  |
        +--------+  : +--->| Cache C |<------->|        |
        |        |  : |    +---------+         |        |
        |  CPU 2 |<---+                        |        |
        |        |  : |    +---------+         |        |
        +--------+  : +--->| Cache D |<------->|        |
                    :      +---------+         |        |
                    :                          +--------+
                    :


想象一下該系統(tǒng)有如下屬性:


 (*) 一個(gè)奇數(shù)號(hào)的cache行可能被緩存在cache A, cache C, 或者可能依然駐留在內(nèi)存中;


 (*) 一個(gè)偶數(shù)號(hào)的cache行可能被緩存在cache B, cache D, 或者可能依然駐留在內(nèi)存中;


 (*) 而當(dāng)CPU核心訪問一個(gè)cache時(shí), 另一個(gè)cache可以同時(shí)利用總線來訪問系統(tǒng)中的其他部
     分 - 可能是替換一個(gè)臟的cache行或者進(jìn)行預(yù)取;


 (*) 每個(gè)cache都有一個(gè)操作隊(duì)列, 被用于保持cache與系統(tǒng)中的其他部分的一致性;


 (*) 當(dāng)LOAD命中了已經(jīng)存在于cache中的行時(shí), 該一致性隊(duì)列并不會(huì)得到?jīng)_刷, 盡管隊(duì)列中
     的內(nèi)容可能會(huì)影響這些LOAD操作. (譯注: 也就是說, 隊(duì)列中有針對(duì)某一cache行的更
     新操作正在等待被執(zhí)行, 而這時(shí)LOAD操作需要讀這個(gè)cache行. 這種情況下, LOAD并不
     會(huì)等待隊(duì)列中的這個(gè)更新完成, 而是直接獲取了更新前的值.)


接下來, 想象一下在第一個(gè)CPU上執(zhí)行兩個(gè)寫操作, 并在它們之間使用一個(gè)寫屏障, 以保證
它們按要求的順序到達(dá)該CPU的cache:


        CPU 1           CPU 2           說明
        =============== =============== =======================================
                                        u == 0, v == 1 并且 p == &u, q == &u
        v = 2;
        smp_wmb();                      確保對(duì)v的修改先于對(duì)p的修改被感知
        <A:modify v=2>                  v的值只存在于cache A中
        p = &v;
        <B:modify p=&v>                 p的值只存在于cache B中


寫內(nèi)存屏障保證系統(tǒng)中的其他CPU會(huì)按正確的順序看到本地CPU cache的更新. 但是設(shè)想一下
第二個(gè)CPU要去讀取這些值的情形:


        CPU 1           CPU 2           說明
        =============== =============== =======================================
        ...
                        q = p;
                        x = *q;


上面這一對(duì)讀操作可 能不會(huì)在預(yù)期的順序下執(zhí)行, 比如持有p的cache行可能被更新到另一個(gè)
CPU的cache, 而持有v的cache行因?yàn)槠渌恍ヽache事件的影響而延遲了對(duì)那個(gè)CPU的cache
的更新:


        CPU 1           CPU 2           說明
        =============== =============== =======================================
                                        u == 0, v == 1 并且 p == &u, q == &u
        v = 2;
        smp_wmb();
        <A:modify v=2>  <C:busy>
                        <C:queue v=2>
        p = &v;         q = p;
                        <D:request p>
        <B:modify p=&v> <D:commit p=&v>
                        <D:read p>
                        x = *q;
                        <C:read *q>     在v被更新到cache之前讀取v
                        <C:unbusy>
                        <C:commit v=2>


基本上, 雖然最終CPU 2的兩個(gè)cache行都將得到更新, 但是在沒有干預(yù)的情況下, 并不能保
證更新的順序跟CPU 1提交的順序一致.


我們需要在兩次LOAD之間插入一個(gè)數(shù)據(jù) 依賴屏障或讀屏障, 以作為干預(yù). 這將強(qiáng)制cache在
處理后續(xù)的請(qǐng)求之前, 先讓它的一致性隊(duì)列得到提交:


        CPU 1           CPU 2           說明
        =============== =============== =======================================
                                        u == 0, v == 1 并且 p == &u, q == &u
        v = 2;
        smp_wmb();
        <A:modify v=2>  <C:busy>
                        <C:queue v=2>
        p = &v;         q = p;
                        <D:request p>
        <B:modify p=&v> <D:commit p=&v>
                        <D:read p>
                        smp_read_barrier_depends()
                        <C:unbusy>
                        <C:commit v=2>
                        x = *q;
                        <C:read *q>     在v被更新到cache之后讀取v


這 些問題會(huì)在DEC Alpha處理器上遇到, 這些處理器使用了分列cache, 通過提高數(shù)據(jù)總線的
利用率以提升性能. 雖然大部分的CPU在讀操作依賴于讀操作的時(shí)候, 會(huì)在第二個(gè)讀操作中
隱含一個(gè)數(shù)依賴屏障, 但是并不是所有CPU都這樣, 因此不能依賴這一點(diǎn).


其他的CPU也可能使用分列cache, 但對(duì)于普通的內(nèi)存訪問, 他們會(huì)協(xié)調(diào)各個(gè)cache列. 而
Alpha 處理器的處理邏輯則取消了這樣的協(xié)調(diào)動(dòng)作, 除非使用內(nèi)存屏障.


cache一致性與DMA
----------------


對(duì) 于進(jìn)行DMA操作的設(shè)備, 并不是所有系統(tǒng)都保持它們的cache一致性. 在這種情況下, 準(zhǔn)備
進(jìn)行DMA的設(shè)備可能從RAM得到陳舊的數(shù)據(jù), 因?yàn)榕K的cache行可能還駐留在各個(gè)CPU的cache
中, 而尚未寫回到RAM. 為了解決這個(gè)問題, 內(nèi)核的相應(yīng)部分必須將cache中重疊的數(shù)據(jù)沖刷
掉(或者使它們失效)(譯注: 沖刷掉cache中的相應(yīng)內(nèi)容, 以保持cache與RAM的一致).


此外, 在設(shè)備已經(jīng)通過DMA將數(shù)據(jù)寫入RAM之后, 這些數(shù)據(jù)可能被cache寫回RAM的臟的cache
行所覆蓋, 或者CPU已緩存的cache行可能直接掩蓋了RAM被更新的事實(shí)(譯注: 使得對(duì)應(yīng)的
LOAD操作只能獲得cache中的舊值, 而無法得到RAM中的新值), 直到cache行被從CPU cache
中丟棄并 且重新由RAM載入. 為解決這個(gè)問題, 內(nèi)核的相應(yīng)部分必須將cache中重疊的數(shù)據(jù)失
效.


更多關(guān)于cache管理的 信息請(qǐng)參閱: Documentation/cachetlb.txt.


cache一致性與MMIO
-----------------


內(nèi) 存映射IO通常通過內(nèi)存地址來觸發(fā), 這些地址是CPU內(nèi)存空間的某個(gè)窗口中的一部分, 而
這個(gè)窗口相比于普通RAM對(duì)應(yīng)的窗口會(huì)有著不同的屬 性.


這些屬性通常包含這樣的情況: 訪存會(huì)完全繞過cache, 而直接到達(dá)設(shè)備總線. 這意味著在
效果上, MMIO可能超越先前發(fā)出的對(duì)被緩存內(nèi)存的訪問(譯注: 意思是, MMIO后執(zhí)行, 但是
先到達(dá)內(nèi)存; 而先執(zhí)行的寫內(nèi)存操作則可能被緩存在cache上, 之后才能沖刷到內(nèi)存). 這種
情況下, 如果這兩者有某種依賴的話, 使用一個(gè)內(nèi)存屏障并不足夠, 而需要在寫被緩存內(nèi)存
和MMIO訪存之間將cache沖刷掉.


=============
CPU 所能做到的
=============


程序員可能會(huì)想當(dāng)然地認(rèn)為CPU將完全按照指定的順序執(zhí)行內(nèi)存操作, 如果CPU是這樣的話,
比方說讓它執(zhí)行下面的代碼:


        a = *A;
        *B = b;
        c = *C;
        d = *D;
        *E = e;


對(duì) 于每一條指令, 他們會(huì)期望CPU在完成內(nèi)存操作之后, 才會(huì)去執(zhí)行下一條指令, 于是系統(tǒng)
中的其他組件將看到這樣一個(gè)明確的操作序列:


        LOAD *A, STORE *B, LOAD *C, LOAD *D, STORE *E.


當(dāng)然, 實(shí)際情況是混亂的. 對(duì)于許多CPU和編譯器來說, 上述假設(shè)不成立, 因?yàn)?


 (*) LOAD操作可能更加需要立即完成以確保程序的執(zhí)行速度(譯注: 因?yàn)橥鶗?huì)有后續(xù)指令
     需要等待LOAD的結(jié)果), 而STORE操作推遲一下往往并不會(huì)有問題;


 (*) LOAD操作可以通過預(yù)取來完成, 并且在確認(rèn)數(shù)據(jù)已經(jīng)不需要之后, 預(yù)取結(jié)果可以丟棄;


 (*) LOAD操作可以通過預(yù)取來完成, 導(dǎo)致結(jié)果被獲取的時(shí)機(jī)可能并不符合期望的執(zhí)行順序;


 (*) 內(nèi)存訪問的順序可能被重新排列, 以促進(jìn)更好的使用CPU總線和cache;


 (*) 有一些內(nèi)存或IO設(shè)備支持對(duì)相鄰地址的批量訪問, 在跟它們打交道的時(shí)候, LOAD和
     STORE操作可能被合并, 從而削減訪存事務(wù)建立的成本, 以提高性能(內(nèi)存和PCI設(shè)備可
     能都可以這樣做); 并且


 (*) CPU的數(shù)據(jù)cache可能影響訪問順序, 盡管cache一致性機(jī)構(gòu)可以緩解這個(gè)問題 - 一旦
     STORE操作命中了cache - 但并不能保證一致性將按順序傳播到其他CPU(譯注: 如果
     STORE操作命中了cache, 那么被更新過的臟數(shù)據(jù)可能會(huì)在cache中停留一段時(shí)間, 而不
     會(huì)立刻沖刷到內(nèi)存中);


所以說, 另一個(gè)CPU可能將上面的代碼看作是:


        LOAD *A, ..., LOAD {*C,*D}, STORE *E, STORE *B


        ("LOAD {*C,*D}"是一個(gè)合并的LOAD)


但 是, CPU將保證自身的一致性: 它將按正確的順序看到自己的內(nèi)存操作, 而不需要使用內(nèi)
存屏障. 以下面的代碼為例:


        U = *A;
        *A = V;
        *A = W;
        X = *A;
        *A = Y;
        Z = *A;


假設(shè)不存在外部的干擾, 那么可以肯定最終的結(jié)果一定是:


        U == the original value of *A
        U == *A的初始值
        X == W
        Z == Y
        *A == Y


對(duì)于上面的代碼, CPU可能產(chǎn)生的全部?jī)?nèi)存訪問序列如下:


        U=LOAD *A, STORE *A=V, STORE *A=W, X=LOAD *A, STORE *A=Y, Z=LOAD *A


然而, 對(duì)于這個(gè)序列, 如果沒有干預(yù), 在保證一致性的前提下, 序列中的一些操作也很可能
會(huì)被合并或丟棄.


在CPU看到這些操作之前, 編譯器也可能會(huì)合并, 丟棄或推遲序列中的一些操作.


例如:


        *A = V;
        *A = W;


可能削減為:


        *A = W;


于是, 在沒有使用寫屏障的情況下, 可以認(rèn)為將V寫入*A的STORE操作丟失了. 類似的:


        *A = Y;
        Z = *A;


在沒有內(nèi)存屏障的情況下, 可能削減為:


        *A = Y;
        Z = Y;


于是在該CPU之外, 根本就看不到有LOAD操作存在.


特別值得一提的 Alpha處理器
-------------------------


DEC Alpha是現(xiàn)有的最為松散的CPU之一. 不僅如此, 許多版本的Alpha CPU擁有分列的數(shù)據(jù)
cache, 允許他們?cè)诓煌臅r(shí)間更新兩個(gè)語義相關(guān)的緩存. 因?yàn)閮?nèi)存一致性系統(tǒng)需要同步更
新系統(tǒng)的兩個(gè)cache, 數(shù)據(jù)依賴屏障在這里就真正成為了必要, 以使得CPU能夠按正確的順序
來處理指針的更新和新數(shù)據(jù)的獲取.


Alpha處理器定義了Linux內(nèi)核的內(nèi)存屏障模 型. (譯注: 體系結(jié)構(gòu)無關(guān)的代碼需要以最壞情
況為基準(zhǔn)來考慮.)


參閱前面的"Cache一致性"章節(jié).


========
使 用示例
========


環(huán)型緩沖區(qū)
----------


內(nèi)存屏障可以用于實(shí)現(xiàn)環(huán)型緩沖 區(qū), 不需要使用鎖, 就能使生產(chǎn)者和消費(fèi)者串行化. 參閱:


        Documentation/circular-buffers.txt


以獲得更多細(xì)節(jié).


====
引 用
====


Alpha AXP Architecture Reference Manual, Second Edition (Sites & Witek,
Digital Press)
        Chapter 5.2: Physical Address Space Characteristics
        Chapter 5.4: Caches and Write Buffers
        Chapter 5.5: Data Sharing
        Chapter 5.6: Read/Write Ordering


AMD64 Architecture Programmer's Manual Volume 2: System Programming
        Chapter 7.1: Memory-Access Ordering
        Chapter 7.4: Buffering and Combining Memory Writes


IA-32 Intel Architecture Software Developer's Manual, Volume 3:
System Programming Guide
        Chapter 7.1: Locked Atomic Operations
        Chapter 7.2: Memory Ordering
        Chapter 7.4: Serializing Instructions


The SPARC Architecture Manual, Version 9
        Chapter 8: Memory Models
        Appendix D: Formal Specification of the Memory Models
        Appendix J: Programming with the Memory Models


UltraSPARC Programmer Reference Manual
        Chapter 5: Memory Accesses and Cacheability
        Chapter 15: Sparc-V9 Memory Models


UltraSPARC III Cu User's Manual
        Chapter 9: Memory Models


UltraSPARC IIIi Processor User's Manual
        Chapter 8: Memory Models


UltraSPARC Architecture 2005
        Chapter 9: Memory
        Appendix D: Formal Specifications of the Memory Models


UltraSPARC T1 Supplement to the UltraSPARC Architecture 2005
        Chapter 8: Memory Models
        Appendix F: Caches and Cache Coherency


Solaris Internals, Core Kernel Architecture, p63-68:
        Chapter 3.3: Hardware Considerations for Locks and
                        Synchronization


Unix Systems for Modern Architectures, Symmetric Multiprocessing and Caching
for Kernel Programmers:
        Chapter 13: Other Memory Models


Intel Itanium Architecture Software Developer's Manual: Volume 1:
        Section 2.6: Speculation
        Section 4.4: Memory Access

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多