內存屏障前世-緩存一致性協議

現代計算機都是多核cpu,cpu需要和內存交互,但內存相對cpu的速度實在太慢,於是cpu和內存之間還有cache層,每個cpu都有屬於自己的cache,cache由cache line組成,每個cache line 64位(根據不同架構,也可能是32位或128位),每個cache line知道自己對應什麼範圍的物理內存地址,當cpu需要讀取某一個內存地址的值時,它會把內存地址傳遞給一級cache,一級cache會檢查它是否有這個內存地址對應的cache line。如果沒有,它會以cache line爲單位從內存加載數據,是的,一次加載整個cache line,這是基於這樣一個假設:內存訪問傾向於本地化(localized),如果我們當前需要某個地址的數據,那麼很可能我們馬上要訪問它的鄰近地址。

 

這是最原始的cpu架構

由於每個cpu獨立工作,那就會有一個顯著的問題:多個cache與內存之間的數據同步該怎麼做?緩存一致性協議就是要解決這個問題,協議有多種,可以分爲兩類:“窺探(snooping)”協議和“基於目錄的(directory-based)”協議,本文所講述的MESI協議屬於一種“窺探協議“。

窺探協議的基本思想

所有cache與內存,cache與cache(是的,cache之間也會有數據傳輸)之間的傳輸都發生在一條共享的總線上,而所有的cpu都能看到這條總線,同一個指令週期中,只有一個cache可以讀寫內存,所有的內存訪問都要經過仲裁(arbitrate)。

窺探協議的思想是,cahce不但與內存通信時和總線打交道,而且它會不停地窺探總線上發生的數據交換,跟蹤其他cache在做什麼。所以當一個cache代表它所屬的cpu去讀寫內存時,其它cpu都會得到通知,它們以此來使自己的cache保持同步。

MESI協議的工作方式

”MESI“該名稱來自4個狀態的首字母的縮寫,協議中最重要的內容有兩部分:cache line的狀態以及消息通知機制。

cache line的狀態有4個:

  • Invalid,表明該cache line已失效,它要麼已經不在cache中,要麼它的內容已經過時。處於該狀態下的cache line等同於它從來沒被加載到cache中。
  • Shared,表明該cache line是內存中某一段數據的拷貝,處於該狀態下的cache line只能被cpu讀取,不能寫入,因爲此時還沒有獨佔。不同cpu的cache line都可以擁有這段內存數據的拷貝。
  • Exclusive,和 Shared 狀態一樣,表明該cache line是內存中某一段數據的拷貝。區別在於,該cache line獨佔該內存地址,其他處理器的cache line不能同時持有它,如果其他處理器原本也持有同一cache line,那麼它會馬上變成“Invalid”狀態。
  • Modified,表明該cache line已經被修改,cache line只有處於Exclusive狀態才能被修改。此外,已修改cache line如果被丟棄或標記爲Invalid,那麼先要把它的內容回寫到內存中。

我們發現,cpu有讀取數據的動作,有獨佔的動作,有獨佔後更新數據的動作,有更新數據之後回寫內存的動作,根據”窺探協議“的規範,每個動作都需要通知到其他cpu,於是有以下的消息機制

  • Read,cpu發起讀取數據請求,請求中包含需要讀取的數據地址。
  • Read Response,作爲Read消息的響應,該消息可能是內存響應的,也可能是某cpu響應的(比如該地址在某cpu cache Line中爲Modified狀態,則該cpu必須返回該地址的最新數據)。
  • Invalidate,cpu發起”我要獨佔一個cache line,其他cpu請失效對應的cache line“的消息,消息中包含了內存地址,所有的其它cpu需要將對應cache line置爲Invalid狀態。
  • Invalidate ACK,收到Invalidate消息的cpu在將對應cache line置爲Invalid後,返回Invalid ACK。
  • Read Invalidate,相當於Read消息+Invalidate消息,即取得數據並且獨佔它,將收到一個Read Response和所有其它cpu的Invalidate ACK。
  • Write back,寫回消息,即將狀態爲Modified的cache line寫回到內存,通常在該行將被替換時使用。現代cpu cache基本都採用”寫回(Write Back)”而非”直寫(Write Through)”的方式。

結合cache line狀態以及消息機制,我們來看看cpu之間是如何協作的。爲了簡化,假設我們有個四核cpu系統,每個cpu只有一個cache line,每個cache line大小爲1個字節,內存地址空間一共兩個字節的數據,地址分別爲0x0和0x8,有如下操作序列:

 

cache line時序變化圖

  1. 初始狀態,4個cpu的cache line都爲Invalid狀態(黑色表示Invalid)。
  2. cpu0發送Read消息,加載0x0的數據,數據從內存返回,cache line狀態變爲Shared。
  3. cpu3發送Read消息,加載0x0的數據,數據從內存返回,cache line狀態變爲Shared。
  4. cpu0發送Read消息,加載0x8的數據,導致cache line被替換,由於之前狀態爲Shared,即與內存中數據一致,可直接覆蓋,而無需回寫。
  5. cpu2發送Read Invalidate消息,從內存返回最新數據,cpu3返回Invalidate ACK,並將狀態變爲Invalid,cpu2獲得獨佔權,狀態變爲Exclusive。
  6. cpu2修改cache line中的數據,cache line狀態爲Modified,同時內存中0x0的數據過期。
  7. cpu1 對地址0x0的數據執行原子(atomic)遞增操作,發出Read Invalidate消息,cpu2將返回Read Response(而不是內存),包含最新數據,並返回Invalidate ACK,同時cache line狀態變爲Invalid。最後cpu1獲得獨佔權,cache line狀態變爲Modified,數據爲遞增後的數據,而內存中的數據仍然爲過期狀態。
  8. cpu1 加載0x8的數據,此時cache line將被替換,由於之前狀態爲Modified,因此需要先執行寫回操作,此時內存中0x0的數據得以更新。

總結

這就是緩存一致性協議,一個狀態機,僅此而已。因爲該協議的存在,每個cpu就可以放心操作屬於自己的cache,而不需要擔心本地cache中的數據會不會已經被其他cpu修改了之類的煩心事。

但到目前爲止,cpu並不滿足,覺得在緩存一致性協議的框架下工作性能不夠高,但這並不是協議的問題,協議本身邏輯很嚴謹,沒毛病(同類協議之間的優劣那是另外一回事)。性能差在哪裏?假如某數據存在於其他cpu的cache中,那自己每次需要修改數據時,都需要發送Read Invalidate消息,除了等待最新數據的返回,還需要等待其他cpu的Invalidate ACK才能繼續執行其他指令,這是一種同步行爲,cpu可忍不了,我們看看cpu如何優化自己?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章