內存障礙: 軟件黑客的硬件視圖

此文爲筆者近日有幸看到的一則關於計算機底層內存障礙的學術論文,並翻譯(機譯)而來[自認爲翻譯的還行],若讀者想要英文原版的論文話,給我留言,我發給你。html

內存障礙: 軟件黑客的硬件視圖

保羅 E. 麥肯尼linux

2009年4月5日c++

 

那麼, 是什麼讓 CPU 設計者使他們對窮人不知情的 SMP 軟件設計者形成內存障礙呢?程序員

簡而言之, 由於從新排序內存引用容許更好的性能, 所以須要內存屏障來強制排序, 如同步基元, 其正確操做依賴於有序內存引用。算法

要得到這個問題的更詳細的答案, 須要對 CPU 緩存的工做方式有一個很好的理解, 特別是使緩存真正工做的須要。如下各節:編程

  1. 呈現緩存的結構,
  2. 描述緩存一致性協議確保 cpu 如何贊成內存中每一個位置的值, 最後,
  3. 概述存儲緩衝區和無效 queueshelp 緩存和緩存一致性協議如何實現高性能。

咱們將看到, 內存屏障是一個必要的邪惡, 這是必須的, 以實現良好的性能和可伸縮性, 這一邪惡的根源在於, cpu 的數量級比二者之間的互聯和他們試圖的內存訪問。緩存

1          緩存結構

現代的 cpu 比現代的內存系統快得多。2006 CPU 可能可以執行每納秒十指令, 但須要許多納秒才能從主內存中提取數據項。這種速度的差別--超過兩個數量級--致使了在現代 cpu 上發現的 multimegabyte 緩存。這些緩存與 cpu 相關聯, 如圖1所示, 一般能夠在幾個週期內進行訪問。[1]安全

 

CPU 0數據結構

CPU 1多線程

覆蓋

覆蓋

記憶

互 連

 

圖 1: 現代計算機系統緩存結構

數據在固定長度塊 (稱爲 "高速緩存線") 中的 cpu 緩存和內存之間流動, 一般是兩個大小的冪, 範圍從16到256字節不等。當給定的數據項首次被給定的 cpu 訪問時, 它將從該 cpu 的緩存中消失, 這意味着出現 "緩存遺漏" (或者更確切地說是 "啓動" 或 "預熱" 緩存缺失)。緩存錯過意味着 CPU 將不得不等待 (或被 "停滯") 數以百計的週期, 而該項目是從內存中提取。可是, 該項將加載到該 CPU 的緩存中, 以便隨後的訪問將在緩存中找到它, 所以以全速運行。

一段時間後, CPU 的緩存將會填滿, 隨後的遺漏可能須要從緩存中彈出一個項目, 以便爲新獲取的項目騰出空間。這樣的緩存錯過被稱爲 "容量錯過", 由於它是由緩存的有限容量形成的。可是, 大多數緩存能夠強制彈出舊項目, 以便爲新項目騰出空間, 即便它們還沒有滿。這是由於, 大型緩存是做爲硬件哈希表實現的, fixedsize 哈希桶 (或 "集合", CPU 設計者調用它們), 沒有連接, 如圖2所示。

此緩存有十六個 "集合" 和兩個 "方法", 總共有32個 "行", 每一個條目包含一個 256byte "緩存行", 它是一個256字節對齊的內存塊。這種高速緩存線的大小在大尺寸上有點小, 但使十六進制算術變得簡單得多。在硬件術語中, 這是一個雙向 setassociative 緩存, 它相似於一個帶有十六個桶的軟件哈希表, 其中每一個桶的哈希鏈最多隻能有兩個元素。大小 (在本例中爲32個緩存行) 和相關性 (在本例中爲兩個) 統稱爲緩存的 "幾何圖形"。因爲此緩存是在硬件中實現的, 所以哈希函數很是簡單: 從內存地址提取四位。

在圖2中, 每一個框對應一個緩存項, 其中能夠包含256字節的緩存行。可是, 緩存項能夠爲空, 如圖中的空框所示。其他的框都用它們所包含的緩存線的內存地址進行標記。因爲緩存行必須是256字節對齊的, 因此每一個地址的低八位爲零, 而硬件哈希函數的選擇意味着下一個較高的四位與哈希行號匹配。

若是程序的代碼位於地址上, 則可能會出現圖中描述的狀況。

                          方式0                  方式1

0x12345000

 

0x12345100

 

0x12345200

 

0x12345300

 

0x12345400

 

0x12345500

 

0x12345600

 

0x12345700

 

0x12345800

 

0x12345900

 

0x12345A00

 

0x12345B00

 

0x12345C00

 

0x12345D00

 

0x12345E00

0x43210E00

 

 

0x0

0x1

0x2

0x3

0x4

0x5

0x6

0x7

0x8

0x9

0xA

0xB

0xC

0xD

0cars

0xF

圖 2: CPU 緩存結構

0x43210E00 經過 0x43210EFF, 該程序經過0x12345EFF 從0x12345000 依次訪問數據。假設程序如今訪問位置0x12345F00。此位置散列到0xF 行, 此行的兩種方式都爲空, 所以能夠容納相應的256字節行。若是程序要訪問位置 0x1233000, 將其哈希爲行 0x0, 則相應的256字節緩存行能夠以1方式容納。可是, 若是程序要訪問位置 0x1233E00 (哈希爲0xE 行), 則必須從緩存中彈出一個現有行, 以便爲新的緩存行騰出空間。若是之後訪問此彈出的行, 則會致使緩存錯過。這樣的緩存小姐被稱爲 "結合小姐"。

迄今爲止, 咱們只考慮 CPU 讀取數據項的狀況。當它寫的時候會發生什麼?由於重要的是全部 cpu 都贊成給定數據項的值, 在給定的 CPU 寫入該數據項以前, 它必須首先使其從其餘 cpu 緩存中刪除或 "失效"。一旦此失效完成, CPU 能夠安全地修改數據項。若是該數據項存在於此 CPU 的緩存中, 但爲只讀, 則此過程稱爲 "寫入遺漏"。一旦給定的 cpu 已完成將給定數據項從其餘 cpu 緩存中做廢, 該 cpu 可能會重複寫入 (並讀取) 該數據項。

之後, 若是另外一個 cpu 嘗試訪問該數據項, 它將招致緩存錯過, 這一次是由於第一個 cpu 使該項無效, 以便寫入它。這種類型的緩存錯過被稱爲 "通訊小姐", 由於一般是因爲幾個 cpu 使用數據項進行通訊 (例如, 鎖是一個數據項, 用於在 cpu 之間使用互斥算法進行通訊)。

顯然, 必須很是當心, 以確保全部 cpu 保持一致的數據視圖。因爲全部這些獲取、無效和寫入, 很容易想象數據丟失或 (也許更糟) 不一樣的 cpu 在各自的緩存中具備與同一數據項有衝突的值。下一節中介紹的 "緩存一致性協議" 防止了這些問題。

2              緩存一致性協議

緩存一致性協議管理緩存行狀態, 以防止數據不一致或丟失。這些協議可能至關複雜, 有許多國家,[2] 但爲了咱們的目的, 咱們只須要關注四狀態 MESI 緩存一致性協議。

2。1     月份狀態

MESI 表明 "修改"、"獨佔"、"共享" 和 "無效", 給定緩存行可使用此協議的四狀態。所以, 使用此協議的緩存除了該行的物理地址和數據外, 還在每一個緩存行上維護兩位狀態 "標記"。

"修改" 狀態中的一行已受來自相應 CPU 的最近內存存儲區的規限, 相應的內存保證不會出如今任何其餘 cpu 的緩存中。所以, "修改" 狀態中的緩存行能夠說是由 CPU "擁有" 的。因爲此緩存包含數據的惟一 todate 副本, 所以此緩存最終負責將其寫入內存或將其分發到其餘緩存中, 而且必須在重用此行以保存其餘數據以前進行。

"獨佔" 狀態與 "修改" 狀態很是類似, 惟一的例外是緩存行還沒有被相應的 CPU 修改, 這反過來意味着駐留在內存中的緩存行數據的副本是 todate 的。可是, 因爲 cpu 能夠隨時存儲到此行, 而無需諮詢其餘 cpu, "獨佔" 狀態下的一行仍然能夠說是由相應的 CPU 擁有的。也就是說, 因爲內存中相應的值是最新的, 因此這個緩存能夠丟棄這些數據, 而不會將其寫回內存或將其交給其餘 CPU。

"共享" 狀態中的行可能至少在另外一個 CPU 的緩存中複製, 所以不容許此 CPU 存儲到該行, 而無需先與其餘 cpu 進行協商。與 "獨佔" 狀態同樣, 由於內存中對應的值是最新的, 因此這個緩存能夠丟棄這些數據, 而沒必要將其寫回內存或將其交給其餘 CPU。

"無效" 狀態中的行爲空, 換言之, 它不包含任何數據。當新數據進入緩存時, 若是可能, 它將被放置在 "無效" 狀態的緩存行中。此方法是首選, 由於替換任何其餘狀態中的行可能會致使昂貴的緩存錯過未來是否引用替換行。

因爲全部 cpu 都必須保持對緩存行中數據的一致視圖, cachecoherence 協議提供了經過系統協調緩存線移動的消息。

2。2          MESI 協議消息

上一節中描述的許多轉換都要求 cpu 之間進行通訊。若是 cpu 在單個共享總線上, 則如下消息足夠:

讀:"讀取" 消息包含要讀取的緩存行的物理地址。

讀取響應:"讀取響應" 消息包含較早的 "讀取" 消息所請求的數據。此 "讀取響應" 消息可能由內存或其餘緩存提供。例如, 若是其中一個緩存在 "已修改" 狀態下具備所需的數據, 則該緩存必須提供 "讀取響應" 消息。

無效:"無效" 消息包含要失效的緩存行的物理地址。全部其餘緩存都必須從緩存中刪除相應的數據並做出響應。

無效確認:接收 "無效" 消息的 CPU 在從其緩存中刪除指定數據後, 必須響應 "無效確認" 消息。

讀取無效:"讀取無效" 消息包含要讀取的緩存行的物理地址, 同時指示其餘緩存刪除數據。所以, 它是一個 "讀" 和 "無效" 的組合, 如其名稱所示。"讀取無效" 消息同時須要 "讀取響應" 和一組 "無效確認" 消息。

"寫回" 消息包含要寫回內存的地址和數據 (可能會 "窺探" 到其餘 cpu 的緩存中)。此消息容許緩存根據須要在 "已修改" 狀態下彈出行, 以便爲其餘數據騰出空間。

有趣的是, 共享內存多處理器系統確實是在封面下傳遞消息的計算機。這意味着使用分佈式共享內存的 SMP 計算機羣集使用消息傳遞在系統體系結構的兩個不一樣級別實現共享內存。

快速測驗 1:若是兩個 cpu 試圖同時使同一高速緩存行無效, 會發生什麼狀況?

 

快速測驗 2:當大型多處理器中出現 "無效" 消息時, 每一個 CPU 都必須發出 "無效確認" 響應。致使 "無效確認" 響應的 "風暴" 不會徹底飽和系統總線嗎?

快速測驗 3:若是 smp 計算機真的使用消息傳遞, 那麼爲何還要用 smp 呢?

2。3          月份狀態圖

給定的緩存行狀態隨着協議消息的發送和接收而更改, 如圖3所示。

 

M

E

S

I

a

c

e

d

f

g

h

j

k

l

b

i

 

圖 3: MESI 緩存-相干態圖

此圖中的過渡弧以下所示:

過渡 (a):緩存行被寫回內存, 但 CPU 將其保留在緩存中, 並進一步保留修改它的權限。此轉換須要 "回寫" 消息。

過渡 (b):CPU 寫入它已經獨佔訪問的緩存行。此轉換不須要發送或接收任何消息。

過渡 (c):CPU 收到已修改的緩存行的 "讀取無效" 消息。CPU 必須使其本地副本無效, 而後響應 "讀取響應" 和 "無效確認" 消息, 既將數據發送到請求的 CPU, 又表示它再也不具備本地副本。

過渡 (d):CPU 對其緩存中未存在的數據項執行原子 readmodify 寫入操做。它傳輸一個 "讀取無效", 經過 "讀取響應" 接收數據。當 CPU 還收到一整套 "無效確認" 響應時, 它就能夠完成轉換。

過渡 (e):CPU 對之前在其緩存中只讀的數據項執行原子 readmodify 寫入操做。它必須傳輸 "無效" 消息, 而且在完成轉換以前必須等待一整套 "無效確認" 響應。

過渡 (f):另外一些 cpu 讀取緩存行, 它是從該 CPU 的緩存中提供的, 它保留一個只讀副本。此轉換是經過接收 "讀取" 消息啓動的, 而此 CPU 用包含所請求數據的 "讀取響應" 消息進行響應。

過渡 (g):另外一些 cpu 讀取此緩存行中的數據項, 並從該 cpu 的緩存或內存中提供。不管哪一種狀況, 此 CPU 都保留一個只讀副本。此轉換是經過接收 "讀取" 消息啓動的, 而此 CPU 用包含所請求數據的 "讀取響應" 消息進行響應。

過渡 (h):這個 CPU 意識到它很快就須要寫入這個緩存行中的一些數據項, 從而傳輸一個 "無效" 消息。CPU 沒法完成轉換, 直到收到一整套 "無效確認" 響應。或者, 全部其餘 cpu 都經過 "回寫" 消息 (想必是爲其餘緩存行騰出空間) 從緩存中彈出此緩存行, 這樣 cpu 就是最後一個緩存它的 cpu。

過渡 ():另外一些 CPU 對緩存行中的數據項執行原子讀-修改-寫入操做, 該運算僅在該 cpu 的緩存中保存, 所以此 cpu 將其從緩存中無效。此轉換是經過接收 "讀取無效" 消息啓動的, 而此 CPU 同時響應 "讀取響應" 和 "無效確認" 消息。

過渡 (j):此 CPU 對緩存行中不在其緩存中的數據項進行存儲, 從而傳輸 "讀取無效" 消息。CPU 沒法完成轉換, 直到接收到 "讀取響應" 和一整套 "無效確認" 消息。當實際存儲完成後, 緩存行大概會經過過渡 (b) 轉換爲 "修改" 狀態。

轉換 (k):此 CPU 在緩存行中加載不在其緩存中的數據項。CPU 傳輸 "讀取" 消息, 並在接收相應的 "讀取響應" 時完成轉換。

過渡 (l):另外一些 CPU 將存儲區放到此緩存行中的數據項, 但因爲該緩存行保存在其餘 cpu 緩存中 (如當前 cpu 的緩存), 所以在只讀狀態下將其保留。此轉換是經過接收 "無效" 消息啓動的, 而且此 CPU 響應 "無效確認" 消息。

快速測驗 4:硬件如何處理上面描述的延遲過渡?

2。4         MESI 協議示例

如今讓咱們從緩存行的數據的角度來看這一點, 最初駐留在地址0的內存中, 由於它經過四 CPU 系統中的各類單行直接映射緩存進行傳輸。表1顯示了此數據流, 其中第一列顯示了操做順序、第二個執行操做的 CPU、第三個執行的操做、下一個四個 cpu 緩存行的狀態 (後跟 MESI 狀態的內存地址) 和最後兩列是否對應的內存內容是最新的 ("V") 仍是不 ("I")。

最初, 數據駐留的 CPU 緩存行處於 "無效" 狀態, 而且數據在內存中有效。當 cpu 0 加載地址0上的數據時, 它進入 cpu 0 的緩存中的 "共享" 狀態, 而且在內存中仍然有效。CPU 3 還加載地址0中的數據, 以便它處於兩個 cpu 緩存中的 "共享" 狀態, 而且在內存中仍然有效。下一個 CPU 3 加載一些其餘緩存行 (地址 8), 它經過回寫將地址0中的數據強制從緩存中取出, 並用地址8中的數據替換它。如今 CPU 2 從地址0進行負載, 可是這個 CPU 意識到它很快就須要存儲到它, 因此它使用一個 "讀取無效" 的消息, 以得到一個獨佔拷貝, 使它從 CPU 3 的緩存無效 (雖然在內存中的副本仍然是最新的)。下一個 CPU 2 會進行預期的存儲, 將狀態更改成 "已修改"。內存中的數據副本如今已過時。cpu 1 使用 "讀取無效" 來窺探 cpu 2 的緩存中的數據並使其失效, 所以 cpu 1 的緩存中的副本處於 "已修改" 狀態 (而且內存中的副本仍然過時)。最後, CPU 1 讀取地址8上的高速緩存行, 它使用 "回寫" 消息將地址0的數據推送回內存。

請注意, 咱們以某些 CPU 緩存中的數據結尾。

快速測驗 5:什麼操做順序會將 cpu 的緩存所有恢復到 "無效" 狀態?

3   商店致使沒必要要的攤位

儘管圖1所示的緩存結構爲從給定的 CPU 重複讀取和寫入給定的數據項提供了良好的性能, 但它對給定緩存行的第一次寫入的性能至關差。要看到這一點, 請考慮圖 4, 它顯示了一個由 cpu 0 寫入到 cpu 1 緩存中的 cacheline 的時間線。因爲 cpu 0 必須等待緩存行到達它才能寫入它, 因此 cpu 0 必須在一段較長的時間內失速。[3]

 

CPU 0

CPU 1

確認

無效

失速

 

圖 4: 寫查看沒必要要的攤位

可是, 沒有真正的理由迫使 cpu 0 拖延這麼長的時間-畢竟, 無論是什麼數據發生在緩存線, cpu 1 發送它, cpu 0 將無條件地覆蓋它。

3。1   存儲緩衝區

防止這種沒必要要的寫入延遲的一種方法是在每一個 CPU 和它的緩存之間添加 "存儲緩衝區", 如圖5所示。隨着這些存儲緩衝區的增長, CPU 0 能夠簡單地在其存儲緩衝區中記錄其寫入並繼續執行。當高速緩存線最終從 cpu 1 轉到 cpu 0 時, 數據將從存儲緩衝區移動到緩存行。

然而, 有些併發症必須解決, 這在接下來的兩個部分中獲得了討論。

3。2        商店轉發

要查看第一個複雜度 (違反 selfconsistency), 請考慮如下代碼, 其中包含變量 "a" 和 "b" (最初爲零), 以及具備最初由 cpu 1 所擁有的變量 "a" 的緩存行, 其中包含 "b" (最初由 cpu 0 擁有):

 

CPU 0

CPU 1

緩衝區

商店

緩衝區

商店

緩存

緩存

記憶

互 連

 

序列

CPU

操做

 

CPU 緩存

 

0

內存8

0

1

2

3

0

 

初始狀態

-/我

-/我

-/我

-/我

V

V

1

0

負荷

0/秒

-/我

-/我

-/我

V

V

2

3

負荷

0/秒

-/我

-/我

0/秒

V

V

3

0

8/秒

-/我

-/我

0/秒

V

V

4

2

RMW

8/秒

-/我

0/E

-/我

V

V

5

2

商店

8/秒

-/我

0/米

-/我

I

V

6

1

原子公司

8/秒

0/米

-/我

-/我

I

V

7

1

8/秒

8/秒

-/我

-/我

V

V

表 1: 緩存一致性示例

圖 5: 帶有存儲緩衝區的緩存

  1. a = 1;
  2. b = 1;
  3. 斷言 (b = = 2);

人們不會指望斷言失敗。然而, 若是一我的愚蠢到使用圖5所示的很是簡單的體系結構, 你會感到驚訝。這樣的系統可能會看到如下一系列事件:

  1. CPU 0 開始執行a=1.
  2. CPU 0 在緩存中看起來是 "a", 並發現

它不見了。

  1. 所以, CPU 0 發送 "讀取無效" 消息, 以便得到包含 "a" 的緩存行的獨佔全部權。
  2. CPU 0 將存儲區記錄到其存儲緩衝區中的 "a"。
  3. CPU 1 接收到 "讀取無效" 消息, 並經過傳輸緩存行並從其緩存中刪除該 cacheline 來響應。
  4. CPU 0 開始執行b=a+1.
  5. cpu 0 從 cpu 1 接收緩存行, whichstill 的值爲 "a" 的零。
  6. CPU 0 從它的緩存中加載 "a", 找到 valuezero。
  7. CPU 0 將該項從其存儲隊列應用到新到達的緩存行, 將其緩存中的 "a" 值設置爲一個。
  8. cpu 0 爲上面的 "a" 加載了一個零值, 並將其存儲到包含 "b" 的緩存行中 (咱們假定它已經由 cpu 0 擁有)。
  9. CPU 0 執行斷言 (b = 2), 但失敗。

問題是, 咱們有兩個 "a" 副本, 一個在緩存中, 另外一個在存儲緩衝區中。

本示例打破了一個很是重要的保證, 即每一個 CPU 將始終看到其本身的操做, 就好像它們是在程序順序中發生的同樣。

這種保證對軟件類型是猛烈的反直覺的, 所以, 硬件的人很同情並實現了 "存儲轉發", 在那裏每一個 CPU 都指 (或 "窺探") 它的存儲緩衝區以及在執行負載時的緩存, 如圖6所示。換言之, 給定 CPU 的存儲直接轉發到其後續負載, 而無需經過緩存。

 

CPU 0

CPU 1

緩衝區

商店

緩衝區

商店

緩存

緩存

記憶

互 連

 

圖 6: 帶有存儲轉發的緩存

隨着存儲轉發到位, 上述序列中的項目8將在存儲緩衝區中找到 "a" 的正確值 1, 以便 "b" 的最終值將是 2, 正如人們所但願的那樣。

3。3 存儲緩衝區和內存障礙

要查看第二個併發症, 即違反全局內存排序, 請考慮如下代碼序列, 變量 "a" 和 "b" 最初爲零:

  1. 無效 foo (空)
  2. {
  3. a = 1;
  4. b = 1;
  5. }

6

  1. 空隙杆 (空)
  2. {
  3. 當 (b = = 0) 繼續;
  4. 斷言 (a = = 1);
  5. }

假設 cpu 0 執行 foo () 和 cpu 1 執行條 ()。進一步假設包含 "a" 的緩存行僅駐留在 cpu 1 的緩存中, 而且包含 "b" 的緩存行由 cpu 0 擁有。而後, 操做序列可能以下所示:

  1. CPU 0 執行a=1.緩存行不在 cpu 0 的緩存中, 所以 cpu 0 將 "a" 的新值放在其存儲緩衝區中, 並傳輸 "讀取無效" 消息。
  2. CPU 1 執行而 (b = = 0) 繼續, 但包含 "b" 的緩存行不在其緩存中。所以, 它傳送一個 "讀取" 信息。
  3. CPU 0 執行b=1.它已經擁有了此緩存行 (換言之, 緩存行已經處於 "已修改" 或 "獨佔" 狀態), 所以它將 "b" 的新值存儲在其緩存行中。
  4. cpu 0 接收到 "讀取" 消息, 並將包含 "b" 的當前更新值的緩存行傳輸到 CPU 1, 也將該行標記爲 "共享" 在其本身的緩存中。
  5. CPU 1 接收到包含 "b" 的緩存行, andinstalls 它在其緩存中。
  6. CPU 1 如今能夠完成執行而 (b = = 0) 繼續, 而且由於它發現 "b" 的值是 1, 它繼續到下一條語句。
  7. CPU 1 執行斷言 (a = = 1), 並且, 因爲 CPU 1 使用的是 "a" 的舊值, 所以此斷言失敗。
  8. cpu 1 接收到 "讀取無效" 消息, 並將包含 "a" 的緩存行傳輸到 CPU 0, 並使此緩存行從其本身的緩存中無效。但爲時已晚。
  9. cpu 0 接收到緩存線, 其中包含 "a" andapplies 緩衝的存儲, 正好是 CPU 1 的失敗斷言的犧牲品。

硬件設計者在這裏不能直接幫助, 由於 cpu 不知道哪些變量是相關的, 更不用說它們是如何相關的了。所以, 硬件設計者提供內存屏障指令, 容許軟件告訴 CPU 有關這種關係。必須更新程序片斷以包含內存屏障:

    1. 無效 foo (空)
    2. {
    3. a = 1;
    4. smp_mb ();
    5. b = 1;
    6. }

7

    1. 空隙杆 (空)
    2. {
    3. 當 (b = = 0) 繼續;
    4. 斷言 (a = = 1);
    5. }

記憶障礙smp_mb ()將致使 CPU 在將後續存儲應用到其緩存行以前刷新其存儲緩衝區。在繼續以前, CPU 能夠簡單地失速直到存儲緩衝區爲空, 或者它可使用存儲緩衝區來保存後續的存儲, 直到應用了存儲緩衝區中的全部前一項。

使用後一種方法, 操做序列可能以下所示:

  1. CPU 0 執行a=1.緩存行不在 cpu 0 的緩存中, 所以 cpu 0 將 "a" 的新值放在其存儲緩衝區中, 並傳輸 "讀取無效" 消息。
  2. CPU 1 執行而 (b = = 0) 繼續, 但包含 "b" 的緩存行不在其緩存中。所以, 它傳送一個 "讀取" 信息。
  3. CPU 0 執行smp_mp (), 並標記全部當前存儲緩衝區項 (即,a=1).
  4. CPU 0 執行b=1.它已經擁有此緩存行 (換言之, 緩存行已經處於 "已修改" 或 "獨佔" 狀態), 但存儲緩衝區中有一個標記的條目。所以, 與其將 "b" 的新值存儲在緩存行中, 不如將其放在存儲緩衝區中 (但在無名項)。
  5. cpu 0 接收到 "讀取" 消息, 並將包含 "b" 的原始值的緩存行傳輸到 CPU 1。它還將其本身的此緩存行的副本標記爲 "共享"。
  6. CPU 1 接收到包含 "b" 的緩存行, andinstalls 它在其緩存中。
  7. CPU 1 如今能夠完成執行而 (b = = 0) 繼續, 但因爲它發現 "b" 的值仍然是 0, 它重複了而聲明。"b" 的新值安全地隱藏在 CPU 0 的存儲緩衝區中。
  8. cpu 1 接收到 "讀取無效" 消息, 並將包含 "a" 的緩存行傳輸到 CPU 0, 並使此緩存行從其本身的緩存中無效。
  9. CPU 0 接收到緩衝存儲區中包含 "a" andapplies 的緩存行。
  10. 由於商店到 "a" 是惟一的條目在 thestore 緩衝區, 是標記的smp_mb (), CPU 0 還能夠存儲 "b" 的新值--除了包含 "b" 的高速緩存行如今處於 "共享" 狀態的狀況以外。
  11. 所以, cpu 0 發送一個 "無效" messageto CPU 1。
  12. cpu 1 接收到 "無效" 消息, 使包含 "b" 的緩存行從其緩存中無效, 並向 CPU 0 發送 "確認" 消息。
  13. CPU 1 執行而 (b = = 0) 繼續, 但包含 "b" 的緩存行不在其緩存中。所以, 它將 "讀取" 消息傳送到 CPU 0。
  14. CPU 0 接收到 "確認" 消息, 並將包含 "b" 的緩存行放入 "獨佔" 狀態。CPU 0 如今將 "b" 的新值存儲到緩存行中。
  15. cpu 0 接收到 "讀取" 消息, 並將包含 "b" 的原始值的緩存行傳輸到 CPU 1。它還將其本身的此緩存行的副本標記爲 "共享"。
  16. CPU 1 接收到包含 "b" 的緩存行, andinstalls 它在其緩存中。
  17. CPU 1 如今能夠完成執行而 (b = = 0) 繼續, 而且由於它發現 "b" 的值是 1, 它繼續到下一條語句。
  18. CPU 1 執行斷言 (a = = 1), 但包含 "a" 的緩存行再也不位於其緩存中。當它從 CPU 0 獲取此緩存時, 它將使用 "a" 的最新值, 所以斷言經過。

如您所見, 此過程不涉及少許簿記。即便是直覺簡單的東西, 好比 "負載一個值", 也可能涉及到許多複雜的硅步驟。

4   商店序列致使沒必要要的攤位

不幸的是, 每一個存儲緩衝區都必須相對較小, 這意味着執行適度的存儲序列的 CPU 能夠填充其存儲緩衝區 (例如, 若是全部這些緩存都致使內存丟失)。此時, CPU 必須再次等待失效完成以耗盡其存儲緩衝區, 而後才能繼續執行。這種狀況可能會當即出如今內存障礙後, 當全部後續存儲指令必須等待失效完成, 而無論這些存儲是否致使緩存丟失。

這種狀況能夠經過使無效確認消息更快到達而獲得改進。實現此目的的一種方法是使用每 CPU 隊列中的無效消息, 或者 "使隊列無效"。

4。1       使隊列無效

使確認消息無效的緣由之一是, 它們必須確保相應的緩存行實際上無效, 若是緩存忙, 則可能會延遲此無效, 例如, 若是 CPU 正在密集地加載和存儲數據都駐留在緩存中。此外, 若是大量無效消息在短期內到達, 則給定的 CPU 在處理它們時可能落後, 所以可能會阻塞全部其餘 cpu。

可是, 在發送確認以前, CPU 不須要實際上使緩存行無效。它能夠對無效消息進行排隊, 並瞭解在 CPU 發送有關該緩存行的任何進一步消息以前將處理該消息。

4。2 使隊列無效並使確認無效

圖7顯示了具備無效隊列的系統。具備無效隊列的 CPU 可能會在隊列中當即確認無效消息, 而沒必要等到相應的行實際失效。固然, 在準備傳輸無效消息時, cpu 必須引用其無效隊列-若是對應緩存行的項在無效隊列中, 則 cpu 沒法當即傳輸無效消息;它必須等待, 直到已處理無效隊列項。

將條目放入無效隊列實質上是 CPU 在傳輸有關該緩存行的任何 MESI 協議消息以前處理該項的承諾。只要對應的數據結構沒有受到高度的爭辯, CPU 就不多會受到這樣的承諾的不便。

可是, 使消息失效的事實能夠在無效隊列中緩衝, 這爲內存 misordering 提供了額外的機會, 以下一節所述。

 

CPU 0

CPU 1

緩衝區

商店

緩衝區

商店

緩存

緩存

無效

隊列

記憶

互 連

無效

隊列

 

圖 7: 帶有無效隊列的緩存

4。3    使隊列和內存障礙無效

假設 "a" 和 "b" 的值最初爲零, 則 "a" 被複製爲只讀 (MESI "共享" 狀態), 而且 "b" 由 CPU 0 (MESI "獨佔" 或 "修改" 狀態) 擁有。而後假設 CPU 0 執行foo ()CPU 1 執行函數時欄 ()在下面的代碼片段中:

  1. 無效 foo (空)
  2. {
  3. a = 1;
  4. smp_mb ();
  5. b = 1;
  6. }

7

  1. 空隙杆 (空)
  2. {
  3. 當 (b = = 0) 繼續;
  4. 斷言 (a = = 1);
  5. }

而後, 操做序列可能以下所示:

  1. CPU 0 執行a=1.對應的緩存行在 cpu 0 的緩存中是隻讀的, 所以 cpu 0 將 "a" 的新值放在其存儲緩衝區中, 並傳輸 "無效" 消息, 以便從 CPU 1 的緩存中刷新相應的緩存行。
  2. CPU 1 執行而 (b = = 0) 繼續, 但包含 "b" 的緩存行不在其緩存中。所以, 它傳送一個 "讀取" 信息。
  3. CPU 0 執行b=1.它已經擁有了此緩存行 (換言之, 緩存行已經處於 "已修改" 或 "獨佔" 狀態), 所以它將 "b" 的新值存儲在其緩存行中。
  4. cpu 0 接收到 "讀取" 消息, 並將包含 "b" 的當前更新值的緩存行傳輸到 CPU 1, 也將該行標記爲 "共享" 在其本身的緩存中。
  5. cpu 1 接收到 "a" 的 "無效" 消息, 將其置於無效隊列中, 並將 "無效確認" 消息傳遞給 CPU 0。請注意, "a" 的舊值仍然保留在 CPU 1 的緩存中。
  6. CPU 1 接收到包含 "b" 的緩存行, andinstalls 它在其緩存中。
  7. CPU 1 如今能夠完成執行而 (b = = 0) 繼續, 而且由於它發現 "b" 的值是 1, 它繼續到下一條語句。
  8. CPU 1 執行斷言 (a = = 1), 並且, 因爲 "a" 的舊值仍然位於 CPU 1 的緩存中, 所以此斷言失敗。
  9. CPU 1 處理排隊的 "無效" 消息, 並使包含 "a" 的緩存行從其本身的緩存中無效。但爲時已晚。
  10. cpu 0 從 cpu 0 接收到 "a" 的 "無效確認" 消息, 所以, 及時將緩衝存儲應用於 cpu 1 的失敗斷言。

再一次, cpu 設計者對這種狀況不能作太多的事情, 由於硬件不知道在 cpu 中有什麼關係只是不一樣的比特。可是, 內存屏障指令能夠與無效隊列交互, 所以, 當給定的 CPU 執行內存屏障時, 它將標記當前處於無效隊列中的全部項, 並強制任何後續負載等待, 直到全部標記的項都已應用於 CPU 的緩存。所以, 咱們能夠添加內存屏障, 以下所示:

    1. 無效 foo (空)
    2. {
    3. a = 1;
    4. smp_mb ();
    5. b = 1;
    6. }

7

    1. 空隙杆 (空)
    2. {
    3. 當 (b = = 0) 繼續;
    4. smp_mb ();
    5. 斷言 (a = = 1);
    6. }

經過此更改, 操做序列可能以下所示:

  1. CPU 0 執行a=1.對應的緩存行在 cpu 0 的緩存中是隻讀的, 所以 cpu 0 將 "a" 的新值放在其存儲緩衝區中, 並傳輸 "無效" 消息, 以便從 CPU 1 的緩存中刷新相應的緩存行。
  2. CPU 1 執行而 (b = = 0) 繼續, 但包含 "b" 的緩存行不在其緩存中。所以, 它傳送一個 "讀取" 信息。
  3. CPU 0 執行b=1.它已經擁有了此緩存行 (換言之, 緩存行已經處於 "已修改" 或 "獨佔" 狀態), 所以它將 "b" 的新值存儲在其緩存行中。
  4. cpu 0 接收到 "讀取" 消息, 並將包含 "b" 的當前更新值的緩存行傳輸到 CPU 1, 也將該行標記爲 "共享" 在其本身的緩存中。
  5. cpu 1 接收到 "a" 的 "無效" 消息, 將其置於無效隊列中, 並將 "無效確認" 消息傳遞給 CPU 0。請注意, "a" 的舊值仍然保留在 CPU 1 的緩存中。
  6. CPU 1 接收到包含 "b" 的緩存行, andinstalls 它在其緩存中。
  7. CPU 1 如今能夠完成執行而 (b = = 0) 繼續, 而且由於它發現 "b" 的值是 1, 它繼續到下一條語句。
  8. CPU 1 執行smp_mb (), 標記其無效隊列中的項。
  9. CPU 1 執行斷言 (a = = 1), 但因爲在無效隊列中包含 "a" 的緩存行有標記的條目, 所以 CPU 1 必須中止此負載, 直到已應用無效隊列中的項爲止。
  10. CPU 1 處理 "無效" 消息, 從其緩存中刪除包含 "a" 的 cacheline。
  11. 如今 CPU 1 能夠自由加載 "a" 的值, butsince 這會致使緩存錯過, 它必須發送一個 "讀取" 消息來獲取相應的緩存行。
  12. cpu 0 從 cpu 0 接收到 "a" 的 "無效確認" 消息, 所以應用緩衝存儲, 將相應緩存行的 MESI 狀態更改成 "已修改"。
  13. cpu 0 接收到 "a" fromCPU 1 的 "讀取" 消息, 所以將相應緩存行的狀態更改成 "共享", 並將緩存線傳輸到 CPU 1。
  14. CPU 1 接收到包含 "a" 的緩存行, 所以能夠執行負載。因爲此負載返回 "a" 的更新值, 斷言經過。

隨着 MESI 消息的大量傳遞, cpu 到達了正確的答案。

5   讀  和  寫  記憶障礙

在上一節中, 內存屏障用於標記存儲緩衝區和無效隊列中的條目。但在咱們的代碼片斷中,foo ()沒有理由作任何與無效隊列, 並欄 ()simlarly 沒有理由對商店排隊作任何事。

所以, 許多 CPU 體系結構提供了較弱的內存屏障指令, 僅執行這兩個操做中的一個或另外一個。粗略地說, "讀取內存屏障" 僅標記無效隊列, 而 "寫入內存屏障" 僅標記存儲緩衝區。而一個徹底成熟的記憶屏障也能作到這一點。

這樣作的效果是, 讀取內存屏障只在執行它的 CPU 上加載, 所以讀取內存屏障以前的全部負載在讀取內存屏障以後的任何負載以前都將顯示完成。一樣, 寫內存屏障命令只存儲, 再次在執行它的 CPU 上, 再次使寫內存屏障以前的全部存儲在寫入內存屏障以後的任何存儲區中都已完成。一個完整的內存屏障命令加載和存儲, 但再次只在 CPU 執行內存屏障。

若是咱們更新Foo和酒吧要使用讀寫內存屏障, 它們的顯示方式以下:

  1. 無效 foo (空)
  2. {
  3. a = 1;
  4. smp_wmb ();
  5. b = 1;
  6. }

7

  1. 空隙杆 (空)
  2. {
  3. 當 (b = = 0) 繼續;
  4. smp_rmb ();
  5. 斷言 (a = = 1);
  6. }

有些電腦有更多的記憶障礙, 但瞭解這三種變體將爲通常的記憶障礙提供一個很好的介紹。

6   例子   記憶屏障序列

本節介紹一些誘人但巧妙地打破記憶障礙的使用。雖然其中許多將在大多數狀況下工做, 有些將一直工做在某些特定的 cpu 上, 但若是目標是生成在全部 cpu 上可靠運行的代碼, 則必須避免使用這些用途。爲了幫助咱們更好地看到微妙的破損, 咱們首先須要關注的是一個有序敵對的體系結構。

6。1            排序敵意體系結構

保羅已經遇到了一些命令敵意的計算機系統, 但敵意的性質一直很是微妙, 並理解它須要詳細的知識, 具體的硬件。與其挑選一個特定的硬件供應商, 並做爲一個大概有吸引力的替代, 以拖動讀者經過詳細的技術規格, 讓咱們取而代之的設計一個神話般的, 但最大的內存排序惡意的計算機體系結構。[4]

此硬件必須服從如下排序約束 [1六、17]:

  1. 每一個 CPU 都會在程序順序中感知到本身的 memoryaccesses。
  2. 若是兩個操做引用不一樣的位置, cpu 將用 storeonly 從新排序給定的操做。
  3. 在讀取內存屏障以前給定 CPU 的全部負載 (smp_rmb ()) 將被全部 cpu 視爲在讀取內存屏障以後的任何負載以前。

 

CPU 0

CPU 1

CPU 2

 

a=1;smp_wmb ();

而 (b = = 0);

 

b=1;

c=1;

z = c;smp_rmb ();x = a;斷言 (z = = 0 | |x==1);

表 2: 內存屏障示例1

  1. 全部給定 CPU 的存儲在 writememory 屏障以前 (smp_wmb ()) 將被全部的 cpu 認爲在任何存儲以後, 寫內存屏障。
  2. 全部給定 CPU 的訪問 (負載和存儲) 以前的完整內存屏障 (smp_mb ()) 將被全部 cpu 視爲在內存屏障以後的任何訪問以前。

快速測驗 6:是否保證每一個 CPU 都能看到本身的內存訪問, 以保證每一個用戶級線程都能按順序看到本身的內存訪問?爲何?

設想一個大型的非統一緩存體系結構 (NUCA) 系統, 以便爲給定節點中的 cpu 提供公平的互連帶寬分配, 並在每一個節點的互鏈接口中提供每一個 cpu 的隊列, 如圖8所示。雖然給定 cpu 的訪問是按該 cpu 執行的內存障礙指定的, 可是, 給定的一對 cpu 訪問的相對順序可能會被嚴重從新排序, 正如咱們將看到的那樣。[5]

6。2     示例1

Figure2 顯示三個代碼片斷, 由 cpu 一、2和3併發執行。每一個 "a"、"b" 和 "c" 都是初始爲零。

 

U 0

Cp

明智

U 1

Cp

e

凹陷

CPU 0

C

交流

CPU 1

節點0

Cp

U 2

明智

Cp

U 3

Mes

聖人

CPU 3

CPU 2

C

交流

節點1

互 連

記憶

 

圖 8: 示例排序-惡意體系結構

假設 cpu 0 最近經歷了許多緩存丟失, 所以其消息隊列已滿, 但 cpu 1 已在緩存中獨佔運行, 所以其消息隊列爲空。而後, cpu 0 對 "a" 和 "b" 的賦值將當即出如今節點0的緩存中 (所以對 cpu 1 可見), 但將被阻塞在 cpu 0 的先前通訊量以後。相比之下, cpu 1 對 "c" 的賦值將經過 cpu 1 之前的空隊列進行航行。所以, cpu 2 在將 cpu 0 的任務分配到 "a" 以前, 極可能會將 cpu 1 的任務分配到 "c", 從而致使斷言發生火災, 儘管存在內存障礙。

理論上, 便攜式代碼不能在這個示例編碼技術上, 然而, 實際上它確實在全部主流計算機系統上工做。

快速測驗 7:是否能夠經過在 CPU 1 的 "同時" 和 "c" 任務之間插入內存屏障來修復此代碼?爲何?

6。3     示例2

Figure3 顯示三個代碼片斷, 由 cpu 一、2和3併發執行。"a" 和 "b" 最初都是零。

一樣, 假設 cpu 0 最近經歷了許多緩存缺失, 所以它的消息隊列已滿, 但 cpu 1 已徹底運行在

      CPU 0         CPU 1                   CPU 2

a=1;

而 (a = = 0);smp_mb ();

y = b;

 

b=1;

smp_rmb ();x = a;斷言 (y = = 0 | |x==1);

表 3: 內存屏障示例2

 

CPU0

CPU1

CPU2

 

1

a=1;

 

 

2

smb_wmb ();

 

 

3

b=1;

而 (b = = 0);

而 (b = = 0);

4

 

smp_mb ();

smb_mb ();

5

 

c=1;

D = 1;

6

而 (c = = 0);

 

 

7

而 (d = = 0);

 

 

8

smp_mb ();

 

 

9

e = 1;

 

斷言 (e = = 0 | |a==1);

表 4: 內存屏障示例3

緩存, 使其消息隊列爲空。而後, cpu 0 對 "a" 和 "b" 的賦值將當即出如今節點0的緩存中 (所以對 cpu 1 可見), 但將被阻塞在 cpu 0 的先前通訊量以後。相比之下, cpu 1 對 "b" 的分配將經過 cpu 1 之前的空隊列進行航行。所以, cpu 2 在將 cpu 0 的任務分配到 "a" 以前, 極可能會將 cpu 1 的任務分配到 "b", 從而致使斷言發生火災, 儘管存在內存障礙。

理論上, 便攜式代碼不能在這個例子編碼技術, 但像之前同樣, 實際上它確實在全部主流計算機系統上工做。

6。4     示例3

Figure4 顯示三個代碼片斷, 由 cpu 一、2和3併發執行。全部變量初始爲零。

請注意, 不管是 cpu 1 仍是 cpu 2 都不能繼續到4行, 直到他們看到 cpu 0 的工做分配到了3行的 "b"。一旦 cpu 1 和2在3行上執行了它們的內存屏障, 它們均可以保證在2行的內存屏障以前, 經過 cpu 0 查看全部分配。一樣, cpu 0 的內存屏障在8號線與 cpu 1 和2行 4, 使 cpu 0 不會執行分配到 "e" 在行 9, 直到它的分配到 "a" 是可見的, 對兩個其餘 cpu。所以, CPU 2 在9行上的斷言是有保證的開火。

快速測驗 8:假設 cpu 1 和2的行3-5 在中斷處理程序中, cpu 2 的行9在進程級別運行。爲了使代碼可以正確工做, 是否須要進行哪些更改, 換言之, 以防止斷言被觸發?

Linux 內核的synchronize_rcu ()基元使用相似於本示例中所示的算法。

7內存屏障指令爲特定的 cpu

每一個 CPU 都有它本身特有的內存屏障指令, 這可使可移植性成爲一個挑戰, 如表5所示。事實上, 許多軟件環境, 包括 pthreads 和 Java, 都只是禁止直接使用內存屏障, 將程序員限制在相互排斥的原語中, 將它們合併到須要的範圍內。在表中, 前四列指示給定的 CPU 是否容許從新排序負載和存儲的四可能組合。接下來的兩列指示給定的 CPU 是否容許使用原子指令從新排序負載和存儲。只有六個 cpu, 咱們有五不一樣的負載存儲 reorderings 組合, 三的四可能的原子指令 reorderings。

第七欄, 依賴讀取從新排序, 須要一些解釋, 這是在下一節涉及 Alpha cpu。簡短的版本是阿爾法要求讀者記憶障礙以及連接數據結構的 updaters。是的, 這意味着 Alpha 能夠實際上獲取數據指向以前它提取指針自己, 奇怪但真實。請參閱:http://www.openvms。compaq.com/wizard/wiz_2637.html若是你認爲我是在作這個。這種極弱的內存模型的好處是 Alpha 能夠

 

LoadsReorderedAfterLoads?

LoadsReorderedAfterStores?

StoresReorderedAfterStores?

StoresReorderedAfterLoads?

AtomicInstructionsReorderedWithLoads?

AtomicInstructionsReorderedWithStores?

DependentLoadsReordered?

IncoherentInstructionCache/管道?

 

阿 爾 法

Y

Y

Y

Y

Y

Y

Y

Y

AMD64

 

 

 

Y

 

 

 

 

IA64

Y

Y

Y

Y

Y

Y

 

Y

(PA-RISC)

Y

Y

Y

Y

 

 

 

 

cpu

 

 

 

 

 

 

 

 

權力Tm

Y

Y

Y

Y

Y

Y

 

Y

(SPARC RMO)

Y

Y

Y

Y

Y

Y

 

Y

(SPARC PSO)

 

 

Y

Y

 

Y

 

Y

SPARC

 

 

 

Y

 

 

 

Y

x86

 

 

 

Y

 

 

 

Y

(x86 OOStore)

Y

Y

Y

Y

 

 

 

Y

zSeries°R

 

 

 

Y

 

 

 

Y

表 5: 內存排序摘要

使用更簡單的緩存硬件, 這反過來又容許在 Alpha 的全盛時期更高的時鐘頻率。

最後一列指示給定的 CPU 是否具備不連貫的指令緩存和管線。此類 cpu 須要爲自修改代碼執行特殊指令。

帶圓括號的 CPU 名稱表示在架構上容許的模式, 但在實踐中不多使用。

常見的 "只是說不" 的方法, 內存障礙能夠很是合理的地方, 它適用, 但有環境, 如 Linux 內核, 在那裏直接使用內存障礙是必需的。所以, Linux 提供了一個精心選擇的 leastcommon 分母的內存屏障基元集, 以下所示:

  • smp mb (): "內存屏障", 它命令裝載和存儲。這意味着內存屏障以前的負載和存儲將在內存屏障以後的任何負載和存儲以前提交給內存。
  • smp 人民幣 (): "讀取內存屏障", 命令只加載。
  • smp wmb (): "寫內存屏障", 只訂購商店。
  • smp 讀取 障礙 取決於 ()這將強制後續操做依賴於要訂購的先前操做。這個原語是除 Alpha 以外的全部平臺上的無運算。
  • mmiowb ()這迫使 MMIO 寫的命令受到全球自旋鎖的保護。在全部平臺上, 自旋鎖的內存屏障都已強制 MMIO 排序, 所以此原始元素是不操做的。nonno 的平臺mmiowb ()定義包括一些 (但不是所有) IA6四、FRV、MIPS 和 SH 系統。這個原始的是相對地新的, 所以相對地少許司機利用它。

的smp mb (),smp 人民幣 ()和smp wmb ()基元還強制編譯器避免任何優化, 這將使內存優化在各個障礙之間從新排序。的smp 讀取障礙取決於 ()基元具備相似的效果, 但僅在 Alpha cpu 上。

這些基元只在 SMP 內核中生成代碼, 可是每一個元素都有一個向上版本 (Smp mb (),smp wmb ()和smp 讀取障礙取決於 (), 分別) 產生內存屏障, 甚至在內核。的Smp 大多數狀況下都應該使用版本。可是, 這些後基元在編寫驅動程序時頗有用, 由於即便在內核中, MMIO 訪問也必須保持有序。在沒有內存屏障指令的狀況下, cpu 和編譯器都將愉快地從新排列這些訪問, 這充其量會使設備發生異常, 並可能致使您的內核崩潰, 甚至會損壞您的硬件。

所以, 大多數內核程序員沒必要擔憂每一個 CPU 的內存屏障特性, 只要它們粘在這些接口上。若是你在一個給定的 CPU 的 architecturespecific 代碼中工做深刻, 固然, 全部的賭注都是假的。

此外, Linux 的全部鎖定基元 (自旋鎖、讀寫器鎖、信號量、區域協調程序、...) 包括任何須要的屏障基元。因此, 若是你正在使用這些基元的代碼, 你甚至不須要擔憂 Linux 的 memoryordering 原語。

也就是說, 對每一個 CPU 的 memoryconsistency 模型的深刻了解能夠在調試時很是有用, 在編寫 architecturespecific 代碼或同步基元時不說什麼。

此外, 他們說, 一點點知識是一件很是危險的事情。試想一下, 你能夠用大量的知識來破壞你的一切!對於那些但願更多地瞭解單個 cpu 的內存一致性模型的人, 接下來的部分將描述那些最流行和最突出的 cpu。雖然沒有什麼能夠代替實際讀取給定 CPU 的文檔, 但這些部分提供了一個很好的概述。

7。1 阿 爾 法

對於一個已經宣佈生命終結的 CPU 來講, 這可能看起來有些奇怪, 可是 Alpha 頗有趣, 由於使用最弱的內存排序模型, 它排序內存操做最積極。所以, 它定義了 Linuxkernel 內存排序基元, 它必須在全部 cpu (包括 Alpha) 上工做。所以, 理解 Alpha 對於 Linux 內核黑客來講是很是重要的。

Alpha 和其餘 cpu 之間的區別由圖9所示的代碼來講明。這smp wmb ()在該圖的9行保證在將元素添加到10行的列表以前執行行6-8 中的元素初始化, 這樣就可使無鎖搜索正常工做。即, 它能夠保證全部 cpu除了阿 爾 法。

Alpha 具備很是弱的內存排序, 所以圖9行20中的代碼能夠看到在6-8 行初始化以前存在的舊垃圾值。

  1. 結構 el * 插入 (長密鑰、長數據)
  2. {
  3. 結構 * p;
  4. p = kmalloc (sizeof (p), GPF_ATOMIC);
  5. spin_lock (互斥);
  6. 下一個 = 頭。
  7. 鑰匙 = 鑰匙;
  8. 數據 = 數據;
  9. smp_wmb ();
  10. 頭. 下一頁 = p;
  11. spin_unlock (互斥);
  12. }

13

  1. 結構 el 搜索 (長鍵)
  2. {
  3. 結構 * p;
  4. p = 頭. 下一頁;
  5. 而 (p! = 和頭) {
  6. 在 ALPHA!!! 上的錯誤*/
  7. 若是 (p-鍵 = = 鍵) {
  8. 返回 (p);
  9. }
  10. p = 下一個;
  11. };
  12. 返回 (NULL);
  13. }

圖 9: 插入和無鎖定搜索

圖10顯示瞭如何在具備分區緩存的積極並行計算機上發生這種狀況, 以便交替緩存線由緩存的不一樣分區處理。假定列表頭頭將由緩存庫0處理, 而且新元素將由緩存庫1處理。在阿爾法,smp wmb ()將保證圖9行6-8 所執行的緩存無效將在10行以前達到互連, 但絕對不能保證新值到達讀取 CPU 核心的順序。例如, 讀取 CPU 的緩存庫1很是忙, 但緩存庫0處於空閒狀態。這可能致使延遲的新元素緩存無效, 所以讀取 CPU 獲取指針的新值, 但會看到新元素的舊緩存值。請參閱前面的網站, 瞭解更多信息, 或者, 若是您認爲我只是在作這些。[6]

人們能夠將一個smp 人民幣 ()原始的是-

編寫 CPU 內核

互 連

讀取 CPU 內核

(r) mb 排序

(r) mb 排序

緩存

銀行0

緩存

銀行1

緩存

銀行0

緩存

銀行1

(w) mb 排序

(w) mb 排序

 

 

 

6

圖 10: 爲何 smp 讀取障礙取決於 () 是必需的

將指針提取和取消引用補間。可是, 這在尊重讀取端的數據依賴項的系統 (如 i38六、IA6四、PPC 和 SPARC) 上強加了沒必要要的開銷。一個smp 讀取障礙取決於 ()已將原始元素添加到 Linux 2.6 內核中, 以消除這些系統的開銷。此原語可用如圖 11 19 行所示。

還能夠實施軟件屏障, 以代替smp wmb (), 這將強制全部讀取 cpu 查看寫入 cpu 的寫入順序。然而, 這種方法被 Linux 社區認爲在極弱有序的 cpu (如 Alpha) 上施加過多的開銷。經過將處理器間中斷 (IPIs) 發送到全部其餘 cpu, 能夠實現此軟件屏障。在收到此類新聞學會後, CPU 將執行內存屏障指令, 實現內存屏障 shootdown。爲了不死鎖, 須要額外的邏輯。固然, 尊重數據依賴關係的 cpu 將定義這樣一個屏障, 以便簡單地smp wmb ().也許這一決定應該在將來從新考慮, 由於阿爾法消失在日落。

Linux 內存屏障基元從 Alpha 指令中取了他們的名字, 因此smp mb ()是M b,smp 人民幣 ()是人民幣和smp wmb ()是wmb.Alpha 是

  1. 結構 el * 插入 (長密鑰、長數據)
  2. {
  3. 結構 * p;
  4. p = kmalloc (sizeof (p), GPF_ATOMIC);
  5. spin_lock (互斥);
  6. 下一個 = 頭。
  7. 鑰匙 = 鑰匙;
  8. 數據 = 數據;
  9. smp_wmb ();
  10. 頭. 下一頁 = p;
  11. spin_unlock (互斥);
  12. }

13

  1. 結構 el 搜索 (長鍵)
  2. {
  3. 結構 * p;
  4. p = 頭. 下一頁;
  5. 而 (p! = 和頭) {
  6. smp_read_barrier_depends ();
  7. 若是 (p-鍵 = = 鍵) {
  8. 返回 (p);
  9. }
  10. p = 下一個;
  11. };
  12. 返回 (NULL);
  13. }

圖 11: 安全插入和無鎖搜索

只有 CPU 的地方smp 讀取障礙取決於 ()是一個smp mb ()而不是一個不作的事

有關 Alpha 的詳細信息, 請參閱參考手冊 [19]。

7。2 AMD64

AMD64 與 x86 兼容, 最近更新了其內存模型 [2], 以強制執行實際實現所提供的更嚴格的順序。Linux 的 AMD64 實現smp mb ()原始是mfence,Smp 人民幣 ()是lfence和smp wmb ()是sfence.理論上, 這些多是放鬆的, 但任何這樣的放鬆必須考慮到 SSE 和3DNOW 指令。

7。3 IA64

IA64 提供了一個弱一致性模型, 所以在沒有顯式內存屏障指令的狀況下, IA64 是在它的權利任意從新排序內存引用 [8]。IA64 有一個內存圍欄指令, 名爲Mf, 但也有 "半內存圍欄" 修飾符加載, 存儲, 並對其一些原子

 

圖 12: 半內存屏障

指示 [7]。的acq修飾符防止隨後的內存引用指令從新排序。acq, 但容許之前的 memoryreference 指令從新排序後,acq, 如圖12所示. 地那些鮮肉。一樣,相對修飾符防止前面的內存引用指令從新排序。相對, 但容許隨後的內存引用指令從新排序。相對.

這些半內存圍欄對於關鍵部分頗有用, 由於將操做推入關鍵部分是安全的, 但這多是致命的, 容許它們流血。可是, 做爲具備此屬性的惟一 cpu 之一, IA64 定義了 Linux 與鎖定獲取和釋放相關的內存排序的語義。

IA64Mf指令用於smp 人民幣 (),smp mb ()和smp wmb ()Linux 內核中的基元。哦, 儘管有相反的謠言, "mf" mneumonic 確實支持 "記憶柵欄"。

最後, IA64 提供了 "發佈" 操做的全局總訂單, 包括 "mf" 指令。這提供了傳遞性的概念, 若是給定的代碼片斷在發生時看到給定的訪問權限, 則之後的代碼片斷也會看到之前的訪問已經發生。假設, 即全部涉及的代碼片斷都正確地使用了內存屏障。

7。4  PA-RISC

儘管 PA RISC 體系結構容許對負載和存儲進行徹底從新排序, 但實際的 cpu 運行徹底有序 [14]。這意味着 Linux 內核的內存排序基元不會生成任何代碼, 可是, 它們確實使用 gcc記憶屬性禁用編譯器優化, 以便在內存屏障上從新排序代碼。

7。5     權力

電源和電源 PC°RCPU 家庭有各類各樣的記憶障礙指示 [6, 15]:

  1. 同步致使全部前面的操做都彷佛有在任何後續操做開始以前完成。所以, 這項指示至關昂貴。
  2. lwsync(輕量同步) 對後續的裝載和存儲以及訂單存儲進行裝載。然而, 它確實對後續負載訂購存儲。有趣的是,lwsync指令強制執行與 zSeries 相同的排序, 巧合的是 SPARC。
  3. eieio(強制按順序執行 i/o, 以防您想知道) 致使全部前面的可緩存存儲在全部後續存儲以前都已完成。可是, 可緩存內存的存儲從存儲區單獨訂購到不可緩存內存, 這意味着eieio不會強制 MMIO 存儲在自旋鎖版本以前。
  4. isync在任何後續指令開始執行以前, 強制全部前面的指令看起來已完成。這意味着前面的指令必須已經取得了足夠的進展, 以致於它們可能生成的任何陷阱都已發生或保證不發生, 而且這些指令的任何反作用 (例如, 頁表更改) 都由隨後的說明。

不幸的是, 這些指令中沒有一個徹底符合 Linux 的wmb ()原始, 這須要全部要訂購的存儲區, 但不須要其餘高開銷的操做同步教學。但沒有選擇: ppc64 版本的wmb ()和mb ()被定義爲重量級同步教學。然而, Linux 的smp wmb ()指令從不用於 MMIO (由於驅動程序必須仔細地訂購 MMIOs 和 SMP 內核, 畢竟), 因此它被定義爲較輕的重量eieio教學。這個指令極可能是獨特的, 有一個五元音 mneumonic, 這表明了 "強制執行的 i/o"。的smp mb ()指令也被定義爲同步指令, 但二者都smp 人民幣 ()和人民幣 ()被定義爲 lighterweightlwsync教學。

功率特徵爲 "cumulativity", 可用於獲取傳遞性。當正確使用時, 查看早期代碼片斷結果的任何代碼也將看到此早期代碼片斷自己所看到的訪問。更多細節從麥肯尼和 Silvera 是可利用的 [18]。

電源結構的許多成員都有不連貫的指令緩存, 所以存儲到內存不必定會反映在指令緩存中。謝天謝地, 如今不多有人編寫自修改代碼, 可是 JITs 和編譯器一直都在這樣作。此外, 從新編譯最近運行的程序看起來就像從 CPU 的角度自修改代碼。的icbi指令 (指令緩存塊無效) 使指定的緩存行從指令緩存中無效, 並可在這些狀況下使用。

7。6      SPARC RMO、PSO 和祖

sparc 上的 Solaris 使用草 (總存儲訂單), Linux 在爲 "SPARC" 32 位體系結構構建時也是如此。可是, 64 位 Linux 內核 ("sparc64" 體系結構) 在 RMO (輕鬆內存順序) 模式下運行 SPARC [20]。SPARC 體系結構還提供了中間 PSO (部分存儲順序)。在 RMO 中運行的任何程序也將在 pso 或左宗棠中運行, 相似地, 在 pso 中運行的程序也將在曹中運行。將共享內存並行程序移動到另外一個方向可能須要當心插入內存屏障, 但如前所述, 使同步基元標準使用的程序沒必要擔憂內存障礙。

SPARC 具備很是靈活的內存屏障指令 [20], 容許對順序進行細粒度控制:

StoreStore: 在後續商店以前訂購前面的商店。(此選項由 Linux 使用smp wmb ()原始。

LoadStore: 在隨後的商店以前訂購前裝運。

StoreLoad: 在後續裝載以前訂購前商店。

LoadLoad: 在隨後的加載以前先訂購以前的負載。(此選項由 Linux 使用smp 人民幣 ()原始。

同步: 在開始任何後續操做以前徹底完成前面的全部操做。

MemIssue: 在後續內存操做以前完成前面的內存操做, 對於內存映射 i/o 的某些實例很重要。

旁路: 與 MemIssue 相同, 但只適用於前面的存儲和隨後的加載, 甚至僅用於訪問相同內存位置的存儲和加載。

的 Linuxsmp mb ()原始使用前四個選項在一塊兒, 如在membar #LoadLoad

|#LoadStore |#StoreStore |#StoreLoad, 從而徹底訂購內存操做。

那麼, 爲何membar #MemIssue須要?由於一個membar #StoreLoad能夠容許隨後的負載從寫入緩衝區獲取其值, 若是寫入的 MMIO 寄存器致使對要讀取的值產生反作用, 則這將是災難性的。相比之下,membar #MemIssue將等到寫入緩衝區刷新後才容許加載執行, 從而確保負載實際上從 MMIO 寄存器得到其值。驅動程序能夠改用membar #Sync, 但重量輕membar #MemIssue是首選的狀況下, 額外的功能更昂貴的membar #Sync不是必需的。

的membar #Lookaside是一個重量較輕的版本membar #MemIssue, 當寫入給定的 MMIO 寄存器時, 它會影響下一個從該寄存器讀取的值。然而, 重的重量membar #MemIssue當對給定的 MMIO 寄存器的寫入影響將從下面讀取的值時, 必須使用其餘一些MMIO 註冊。

不清楚 SPARC 爲何不定義wmb ()要被membar #MemIssue和smb wmb ()要被membar #StoreStore, 由於當前的定義彷佛容易受到某些驅動程序中的 bug 的影響。Linux 運行的全部 SPARC cpu 都有可能實現比體系結構更保守的內存訂購模型。

SPARC 須要一個沖洗指令在存儲和執行指令的時間之間使用 [20]。須要從 SPARC 的指令緩存中刷新該位置的任何先前值。注意,沖洗獲取地址, 而且只從指令緩存中刷新該地址。在 SMP 系統上, 全部 cpu 的緩存都被刷新, 可是沒有任何方便的方法來肯定 cpu 的刷新什麼時候完成, 儘管有一個對實現說明的引用。

7。7 x86

因爲 x86 cpu 提供 "進程排序", 所以全部 cpu 都贊成給定 CPU 寫入內存的順序, 則smp wmb ()原始是 noop 爲 CPU [10]。可是, 須要編譯器指令來防止編譯器執行優化, 從而致使在整個smp wmb ()原始。

另外一方面, x86 cpu 傳統上沒有給出負載的排序保證, 所以smp mb ()和smp 人民幣 ()基元擴展到鎖; addl.這種原子指令對負載和存儲都是一個障礙。

最近, 英特爾發佈了 x86 [11] 的內存模型。事實證實, 英特爾的實際 cpu 比之前的規範中要求的要嚴格, 因此這個模型實際上只是受權先前的實際行爲。最近, 英特爾爲 x86 [12] 發佈了一個更新的內存模型, 它爲存儲提供了一個全局訂單, 儘管單個 cpu 仍被容許看到它們本身的存儲, 這比全球訂單以前的狀況更早。爲了容許涉及存儲緩衝區的重要硬件優化, 須要對總訂單進行此異常。軟件可使用原子操做來覆蓋這些硬件優化, 這也是原子操做比非原子對應更昂貴的一個緣由。此總存儲訂單是在較舊的處理器上獲得保證。

可是, 請注意, 一些 SSE 指令是弱有序的 (clflush和非世俗移動指示 [9])。具備 SSE 的 cpu 可使用mfence爲smp mb (),lfence爲smp 人民幣 ()和sfence爲smp wmb ().

x86 CPU 的幾個版本有一個模式位, 它啓用了順序存儲, 對於這些 cpu,smp wmb ()也必須定義爲鎖; addl.

儘管許多舊的 x86 實現都知足了自修改代碼, 而不須要任何特殊的指令, 但 x86 體系結構的更新修改再也不要求 x86 cpu 如此適合。有趣的是, 這種放鬆來得正是時候, 給 JIT 實現者帶來不便。

7。8 zSeries

TmzSeries 機組成了 IBM 大型機家族, 之前稱爲360、370和 390 [13]。zSeries 的並行性來得晚, 但考慮到這些大型機最先在二十世紀六十年代中旬出貨, 這並非說得太多。的bcr 150指令用於 Linuxsmp mb (),smp 人民幣 ()和smp wmb ()原。它還具備相對強的內存排序語義, 如表5所示, 它應容許smp wmb ()原始的是一個nop(當你讀到這個時, 這種變化極可能發生了)。該表實際上低估了這種狀況, 由於 zSeries 內存模型在其餘順序上是一致的, 這意味着全部 cpu 都將贊成來自不一樣 cpu 的無關存儲的順序。

與大多數 cpu 同樣, zSeries 體系結構不保證緩存連貫的指令流, 所以, 自修改代碼必須在更新指令和執行它們之間執行序列化指令。換句話說, 許多實際的 zSeries 機器其實是在不序列化指令的狀況下適應自修改代碼的。zSeries 指令集提供了一大組序列化指令, 包括比較和交換、某些類型的分支 (例如, 上述bcr 150指令), 以及測試和設置等。

8   記憶障礙是否永遠存在?

最近的一些系統在通常的順序執行方面明顯不那麼激進, 特別是從新訂購內存引用。這種趨勢會持續到記憶障礙是過去的問題嗎?

同意的論據將引用建議的大規模多線程硬件體系結構, 以便每一個線程都將等待, 直到內存準備就緒, 十個, 數以百計, 甚至數以千計的其餘線程在此期間取得進展。在這樣的體系結構中, 不須要內存屏障, 由於給定的線程在繼續下一個指令以前, 只需等待全部未完成的操做。因爲可能會有數以千計的其餘線程, cpu 將被徹底利用, 所以不會浪費 cpu 時間。反對的論據將引用數量極有限的應用程序, 可以擴展多達1000個線程, 以及日益嚴重的實時需求, 對於某些應用程序來講, 這是一個微秒。因爲大量多線程方案所隱含的極低的單線程吞吐量, realtimeresponse 的要求很是困難, 難以知足。

另外一種同意的論據將會提到愈來愈複雜的滯後時間隱藏硬件實現技術, 這極可能容許 CPU 提供徹底按順序一致執行的錯覺, 同時仍然提供幾乎全部的順序執行的性能優點。

相反的論點將會提到電池操做設備和環境責任所帶來的愈來愈嚴重的電力效率要求。

誰是對的?咱們沒有頭緒, 因此準備住在任何一種狀況下。

9   對硬件設計人員的建議

硬件設計者能夠作許多事情來使軟件人的生活變得困難。下面是咱們過去遇到過的一些這樣的事情的清單, 在這裏提出, 但願它可能有助於防止將來的這些問題:

  1. 忽略緩存一致性的 i/o 設備。

這個迷人的 misfeature 會致使 DMAs 從內存中丟失最近對輸出緩衝區所作的更改, 或者一樣糟糕的是, 在 DMA 完成後, CPU 緩存的內容會覆蓋輸入緩衝區。要使您的系統可以正常工做, 您必須仔細刷新任何 DMA 緩衝區中任何位置的 CPU 緩存, 而後再將該緩衝區呈現給 i/o 設備。即便這樣, 你也須要很是當心避免指針錯誤, 由於即便是誤讀到輸入緩衝區也會致使數據輸入損壞!

  1. 忽略緩存一致性的設備中斷。

這聽起來可能不夠天真-畢竟, 中斷不是內存引用, 是嗎?可是想象一下, 一個帶有拆分緩存的 CPU, 其中一個銀行很是忙, 所以保持到輸入緩衝區的最後一個 cacheline。若是相應的 i/o 徹底中斷到達此 cpu, 則 cpu 對緩衝區最後緩存行的內存引用可能會返回舊數據, 再次致使數據損壞, 但在之後的崩潰轉儲中將不可見的窗體中。當系統繞過有衝突的輸入緩衝區時, DMA 極可能已經完成。

  1. 處理器間中斷 (IPIs), ignorecache 一致性。

若是在相應消息緩衝區中的全部緩存行都已提交內存以前, 新聞學會到達其目標, 這可能會有問題。

  1. 提早到達緩存一致性的上下文切換。

若是內存訪問能夠完成過於瘋狂的順序, 那麼上下文切換多是至關悲慘的。若是任務在源 cpu 可見的全部內存訪問以前掠過到另外一個 cpu, 那麼任務就能夠很容易地看到相應的變量恢復到之前的值, 這會使大多數算法產生致命的混淆。

  1. 過於親切的模擬器和仿真器。

很難編寫模擬器或仿真程序來強制內存從新排序, 所以在這些環境中運行正常的軟件會在第一次在真正的硬件上運行時獲得使人不快的驚喜。不幸的是, 它仍然是規則, 硬件是比模擬器和仿真器更狡猾, 但咱們但願這種狀況改變。

再次, 咱們鼓勵硬件設計者避免這些作法!

確認

我感謝許多 CPU 架構師耐心地解釋他們的 cpu 的指令和 memoyr 的功能, 特別是韋恩 Cardoza, Ed Silha, 安東. ·弗雷, 凱茜, 德里克. 威廉斯, 蒂姆 Slegel, 史塔克 Probst, Ingo Adlung, 和拉維 Arimilli韋恩值得特別感謝他的耐心解釋字母從新排序的從屬負載, 一個教訓, 我抗拒至關 strenu-顯然!

法律聲明

此工做表明做者的視圖, 不必定表明 IBM 的視圖。

IBM、zSeries 和電源 PC 是美國、其餘國家或二者的國際商用機器公司的商標或註冊商標。

Linux 是萊納斯托瓦爾茲的註冊商標。i386 是英特爾公司或其子公司在美國、其餘國家或二者的商標。其餘公司、產品和服務名稱多是此類公司的商標或服務標誌。版權全部°c 2005 IBM 公司。

快速測驗的答案

快速測驗 1:

回答:若是兩個 cpu 試圖同時使同一高速緩存行無效, 會發生什麼狀況?

其中一個 cpu 首先得到共享總線的訪問權限, cpu "獲勝"。另外一個 cpu 必須使其緩存行的副本無效, 並向另外一個 cpu 發送 "無效確認" 消息。固然, 丟失的 cpu 能夠被指望當即發出 "讀取無效" 的交易, 所以獲勝的 cpu 的勝利將是至關短暫的。

快速測驗 2:

回答:當大型多處理器中出現 "無效" 消息時, 每一個 CPU 都必須發出 "無效確認" 響應。致使 "無效確認" 響應的 "風暴" 不會徹底飽和系統總線嗎? 若是大規模多處理器其實是以這種方式實現的, 它可能。較大的多處理器, 特別是 NUMA 機器, 傾向於使用所謂的 "基於目錄的" 緩存一致性協議, 以免出現此問題和其餘難題。

快速測驗 3:

回答:若是 smp 計算機真的使用消息傳遞, 那麼爲何還要用 smp 呢? 在過去的幾十年裏, 關於這個問題有至關多的爭議。一個答案是, 緩存一致性協議很是簡單, 所以能夠直接在硬件中實現, 從而得到軟件消息傳遞沒法達到的帶寬和延遲。另外一個答案是, 因爲大型 smp 機器的相對價格, 以及小型 smp 機器的集羣, 在經濟學中發現了真正的真理。第三個答案是 SMP 編程模型比分佈式系統更容易使用, 但反駁可能會注意到 HPC 集羣和 MPI 的出現。因此爭論還在繼續。

快速測驗 4:

回答:硬件如何處理上面描述的延遲過渡?

一般是經過添加其餘狀態, 儘管這些附加的狀態沒必要實際存儲在緩存行中, 這是由於每次只有幾行會轉換。延遲轉換的須要只是一個問題, 致使現實世界的緩存一致性協議比本附錄中描述的超簡化 MESI 協議複雜得多。軒尼詩和帕特森對計算機體系結構的經典介紹 [5] 涵蓋了許多這些問題。

快速測驗 5:

回答:什麼操做順序會將 cpu 的緩存所有恢復到 "無效" 狀態?

 

沒有這樣的序列, 至少在沒有特殊的 "刷新個人緩存" 指令在 CPU 的指令集。大多數 cpu 都有這樣的指令。

快速測驗 6:

回答:是否保證每一個 CPU 都能看到本身的內存訪問, 以保證每一個用戶級線程都能按順序看到本身的內存訪問?爲何?

不。請考慮線程從一個 CPU 遷移到另外一箇中央處理器的狀況, 以及目標 cpu 對源 cpu 最近的內存操做的感知不有序的狀況。爲了保持用戶模式的健全, 內核黑客必須在上下文切換路徑中使用內存屏障。可是, 安全執行上下文切換所需的鎖定將自動提供致使用戶級任務按順序查看其自身訪問所需的內存障礙。若是您正在設計一個超級優化的調度程序, 不管是在內核仍是在用戶級別, 請牢記此方案!

快速測驗 7:

回答:是否能夠經過在 CPU 1 的 "同時" 和 "c" 任務之間插入內存屏障來修復此代碼?爲何?

不。這樣的內存屏障只會強制命令本地 CPU 1。它不會對 cpu 0 和 cpu 1 訪問的相對排序產生影響, 所以斷言仍可能失敗。然而, 全部主流計算機系統提供一種或另外一個機制來提供 "傳遞性", 這提供了直觀的因果排序: 若是 b 看到了 a 的訪問效果, c 看到了 b 的訪問效果, 那麼 c 也必須看到 a 的訪問效果。.

快速測驗 8:

回答:假設 cpu 1 和2的行3-5 在中斷處理程序中, cpu 2 的行9在進程級別運行。爲了使代碼可以正確工做, 是否須要進行哪些更改, 換言之, 以防止斷言被觸發? 斷言將須要編碼, 以確保 "e" 的負載在 "a" 以前。在 Linux 內核中, 屏障 () 原語可用於實現這一點, 這與前面示例中斷言中使用內存屏障的方式很是類似。

引用

  1. 先進的微型設備.AMD x86-64 體系結構程序員手冊卷1-5, 2002。
  2. 先進的微型設備.AMD x86-64 體系結構程序員手冊卷 2: 系統編程, 2007。
  3. Culler, d.e., 辛格, jp 和古普塔, A。並行計算機體系結構: 硬件/軟件方法.摩根考夫曼,

1999。

  1. Gharachorloo, K。共享內存多處理器的內存一致性模型。斯坦福大學電子工程與計算機科學系 CSL-TR-95-685, 計算機系統實驗室, 斯坦福, 加州, 1995年12月。可用:http://www.hpl.hp.com/techreports/康柏-東汽/WRL-95-9. pdf[查看: 2004年10月11日]。
  2. 軒尼詩, j.l. 和帕特森, 檢察官計算機體系結構: 一種定量方法.摩根考夫曼, 1995。
  3. IBM 微電子與摩托羅拉.PowerPC 微處理器系列: 編程環境, 1994。
  4. 英特爾公司.英特爾安騰體系結構軟件開發商手冊卷 3: 指令集參考, 2002。
  5. 英特爾公司.英特爾安騰體系結構軟件開發人員手冊卷 3: 系統體系結構, 2002。
  6. 英特爾公司.IA-32 英特爾體系結構軟件開發商手冊卷 2B: 指令集參考, N Z, 2004。可用:ftp://download.intel.com/design/Pentium4/手冊/25366714. pdf[查看: 2005年2月16日]。
  7. 英特爾公司.IA-32 英特爾體系結構軟件開發商手冊卷 3: 系統編程指南, 2004。可用:ftp://download.intel.com/design/Pentium4/手冊/25366814. pdf[查看: 2005年2月16日]。
  8. 英特爾公司.英特爾64體系結構內存訂購白皮書, 2007。可用:http://developer.intel.com/產品/處理器/手冊/318147. pdf[查看: 2007年9月7日]。
  9. 英特爾公司.英特爾64 IA-32 體系結構軟件開發人員手冊, 3A: 系統編程指南, 1 部分, 2009。可用:http://download.intel。com/設計/處理器/手冊/253668. pdf[查看: 2007年9月7日]。
  10. 國際商用機器公司.z/體系結構原理的操做。可用:http://publibz.boulder。ibm.com/epubs/pdf/dz9zr003.pdf[查看: 2005年2月16日], 2004年5月。
  11. 凱恩灣RISC 2.0 體系結構.HewlettPackard 專業書1996。
  12. 里昂, m, Silha, E 和乾草, B。PowerPC 存儲模型和 AIX 編程。可用:http://www-106.ibm.com/developerworks/eserver/文章/powerpc. html[查看: 2005年1月31日], 2002年8月。
  13. 麥肯尼, 體育現代微處理器中的內存排序, 第一部分。Linux 雜誌1, 136 (2005年8月), 52–57。可用:http

www.linuxjournal.com/article/8211 http://www.rdrop.com/users/paulmck/可伸縮性/紙張/訂購2007.09.19a。Pdf[查看 2007年11月30日]。

  1. 麥肯尼, 體育現代微處理器中的內存排序, 第二部分。Linux 雜誌1, 137 (2005年9月), 78–82。可用:http

www.linuxjournal.com/article/8212 http://www.rdrop.com/users/paulmck/可伸縮性/紙張/訂購2007.09.19a。Pdf[查看 2007年11月30日]。

  1. 麥肯尼, 體育和 Silvera, R。c++ 內存模型的電源實現示例。可用:http://www.rdrop.com/users/paulmck/scalability/paper/N2745r 2009.02.27a. html[查看: 2009年4月5日], 2009年2月。
  2. 站點、r.l. 和 Witek, 室溫阿爾法 AXP

建築, 第二版數字媒體, 1995。

  1. SPARC 國際.SPARC 體系結構手冊, 1994。

 

[1]使用多個級別的高速緩存是標準的作法, 一個小型的高速緩存接近 CPU, 具備單週期訪問時間, 而且具備更長的訪問時間 (大約爲十時鐘週期) 的更大級別兩個緩存。高性能 cpu 一般具備三甚至四級緩存。

[2]請參見 Culler et. [3] 頁670和671爲 SGI Origin2000 和相繼 (如今 IBM) NUMA Q 分別的九狀態和26狀態圖。兩個圖都比實際生活簡單得多。

[3]將高速緩存線從一個 CPU 的緩存傳輸到另外一臺處理器所需的時間一般比執行簡單寄存器到寄存器指令所需的數量多一些。

[4]讀者更傾向於詳細查看真正的硬件體系結構, 以諮詢 CPU 供應商手冊 [1九、一、八、六、1五、20、十、九、13] 或 Gharachorloo 的論文 [4]。

[5]任何真正的硬件架構師或設計師無疑會大聲呼籲拉爾夫在瓷對講機, 由於他們可能只是有點心煩的前景的工做, 其中一個隊列應該處理的消息涉及緩存線, 這兩個 cpu 訪問, 對這個例子所構成的許多種族沒有任何發言權。我只能說 "給我一個更好的例子"。

[6]固然, 精明的讀者將已經認識到, 阿爾法幾乎沒有任何卑鄙和骯髒的, 由於它多是, (謝天謝地) 神話建築在6.1 節是一個例子。

相關文章
相關標籤/搜索