CPU, Memory Barrier, Cache Coherence

來源:互聯網
上載者:User

亂序也分讀/寫,在 x86 IA32 這種體系下只有“讀亂順”,而沒有“寫亂序”,
在 x86 IA32 下“寫”是嚴格順序的。對於軟體來說,為了保證 CPU流水線的
效率提升不導致混亂,確保 process ordering 與 program ordering 的一致
性問題,而使用了“compiler barrier”,“memory barrier”(下面會講到),
在某些平台下,如 WINDOWS VISTA 與 WINDOWS 7 以前的版本 “memory barrier”
的實現基本是發送 lock# 訊號到北橋,由它來鎖住 host BUS。關於這點可以查
看早期 WINDOWS 的 KeMemoryBarrier() 函數,其基本上就是一條 xchg 指令
(熟悉 x86 IA32 的人都知道,即使沒有 lock 首碼,該指令一樣會發送 lock#
訊號)。

在 x86 IA32 下,可以說所有 “memory barrier” 特有指令能做的事,lock
指令同樣能做到。但這並不表明就應該使用 lock 指令來替代“memory barrier”
特有指令。我們都知道在 SMP 下 spinlock 的實現就是基於 lock 指令的,而使
用這種粗粒度的指令來完成“memory barrier” 結果就是效率上的損失。尤其是
在 SMP 上,由於 x86 IA32 使用的 Cache Snooping 協議,當 CPU-A 發送 lock#
訊號鎖住 host BUS,即 MRM 佔用 host BUS,此時 CPU-B 即 LRM 的 PHIT 和
PHITM 對 MRM 進行監聽,若 MRM 操作命中 LRM 某有效 CACHE LINE 則 PHIT 受
信,若命中修改狀態的某有效 CACHE LINE 則 PHITM 受信,接下來 MRM 就會
INVALIDATE 此 CACHE LINE。(P6 後的實現有所不同,lock# 不會鎖 host BUS,
而是用 CACHE lock 實現,這樣雖然比鎖 host BUS 效率高但仍不是好的解決方案)
在使用 xFENCE 等指令來實現“memory barrier”的情況下,要比使用 lock 指令
效率高很多。其實在 PIII 後就已經提供了 sfence、lfence、mfence 這些專用於
“memory barrier”的指令,這些指令在 WINDOWS VISTA 與 WINDOWS 7 中可見。
可能是為了考慮通用與一致性問題,這些指令在以前的版本中沒有使用。

前面說過在 x86 IA32 下“寫” 是嚴格順序的。但其他 RISC 平台上的開發就沒
那麼幸運了,這些問題必須自己考慮,我在 PWOER CPU 的 AIX Kernel 下就碰到
過這種問題。還有在 EPIC 下,需要做的還有很多。

上面提到的 “compiler barrier”,“memory barrier”。先看下 “compiler
barrier” 的具體應用,現代編譯器通常會充分利用該體系 CPU 的亂序功能來進
行最佳化。在編程中定義一個變數時,如果不想讓編譯器對其進行亂序最佳化可以使
用 volatile 關鍵字。如: volatile int *p1; 這樣則基本上可以保證編譯出的
代碼沒有對其進行亂序處理。可以反組譯碼驗證如下代碼的區別。

int *p1;  
volatile int *p1 //以不同方式定義 p1;

int *p2;
......// 略掉一系列為 p1,p2 分配記憶體等操作。
*p1 = 1;
*p1 = 2;
*p2 = *p1;

但這僅是保證編譯出來的代碼不會亂序。再來看下 “memory barrier” 的具體
應用。首先要知道 CPU 在什麼情況下才會亂續執行?什麼樣的代碼不會被亂續
執行?以下面的結構為例。

typedef struct _barrier{
     int  n1;
     int  n2;
}barrier;

先看如下代碼。CPU 是的亂序執行是不會先執行後一條指令的。也就是說此代碼
的 mybarrier.n2 = mybarrier.n1; 不會在 mybarrier.n1 = 0; 之前執行,因為
CPU 的預測執行會檢測到代碼的依賴關係,此類依賴稱之為 WAW(write after
write )依賴(還有 RAW,WAR 依賴)。所以無須添加“memory barrier”。

barrier mybarrier;
mybarrier.n1 = 0;
mybarrier.n2 = mybarrier.n1;

再看改動後的代碼前,設想以下情景,假設有一塊 PCI/PCIE 裝置,你為其寫
DRIVER。在通過 PCI 的 BDF 得到其 PCI Configuration Space 後,根據 PCI BAR
的最後一位為 0,表示為 MMIO。此時,你將這個狀態記錄到你的結構中去如:
“mybarrier.n1 = 1;”,然後設定“mybarrier.n2 = 0;” 表示裝置準備就緒,
可以進行操作。

mybarrier.n1 = 1;
mybarrier.n2 = 0;
......
......

CPU 的亂序預測很可能會先執行 n2 = 0。然後再執行 n1 = 1; 如果這時你取記錄
n1 很可能得到的是 0,從而誤認為你的 PCI 裝置是 PORT I/O 方式。也就是說如
果你的代碼是要根據前一條指令的結果來決定後一條指令,那麼這樣編寫就很有可
能出現問題。說白了就是在 CPU 看來兩個不相關的變數地址讀/寫會預測亂序執行。
為了保證代碼不亂序執行才提出了 “memory barrier”記憶體屏障的概念。也就是
在你的代碼中要顯示的加入一些函數來保證執行時的順序。此類指令最終是與體系
相關的,在不同平台上所用的彙編指令不同。如 POWER AIX 上的
“memory barrier”則是由 sync, eieio, lwsync 等指令實現。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.