硬件緩存策略

操作系統中的存儲器構成了一個金字塔,越往上的存儲器速度越快,但是價格也越貴,所以也就越小。爲了解決高速的處理器和低速的存儲器之間的矛盾,上一層的存儲器作爲下一層存儲器的緩存。

在現代的CPU(大多數)上,所有的內存訪問都需要通過層層的緩存來進行。CPU的讀/寫(以及取指令)單元正常情況下甚至都不能直接訪問內存——這是物理結構決定的;CPU都沒有管腳直接連到內存。相反,CPU和一級緩存(L1 Cache)通訊,而一級緩存才能和內存通訊。大約二十年前,一級緩存可以直接和內存傳輸數據。如今,更多級別的緩存加入到設計中,一級緩存已經不能直接和內存通訊了,它和二級緩存通訊——而二級緩存才能和內存通訊。或者還可能有三級緩存。你明白這個意思就行。

比如要需要操作內存的某個區域時,處理器不會直接去內存讀取,而是會去高速緩存中查看該區域是不是被調進來了,如果沒有,則把該區域調入高速緩衝區中。那麼接下來處理器直接在高速緩存中進行讀寫操作。

同樣的,對於讀取磁盤數據,處理器把內存中的某塊區域作爲磁盤的緩存。那麼便可以直接在內存中進行讀寫。

處理器在緩存中對數據進行讀寫操作,但是還是需要把該數據寫會到原來的區域中去,這接涉及到了一定的策略。

 

寫緩存策略

(1)第一種策略稱爲不緩存(nowrite),也就是說高速緩存不去緩存任何寫操作。當對緩存中的數據進行寫時,將直接跳過緩存,直接寫到磁盤,同時標記緩存的數據失效。如果後續需要進行讀操作,需要重新從磁盤讀取數據。

(2)第二種策略稱爲寫透緩存(write-through cache),即寫操作將自動更新緩存,同時也更新磁盤文件。這種操作對保持緩存一致性很有好處,所以不需要將緩存標記爲失效,同時實現也比較簡單。

(3)第三種策略,也是Linux所採用的,稱爲回寫(write-back)。在這種策略下,程序執行寫操作直接寫到緩存中,但是不會直接更新磁盤,而是將高速緩存中被寫入的頁面標記成“髒”,並加入髒頁鏈表中。然後由一個進程(回寫進程)週期性將髒頁鏈表的頁寫會到磁盤,從而讓磁盤中的數據和緩存中的數據一致,最後清理緩存的“髒”頁標誌。“髒”的意思不是說數據不乾淨,而是說數據沒有同步到磁盤。

 

緩存一致性策略

現在的的多處理器的計算機,每個CPU都有自己的寄存器和緩存。那麼一個多線程的程序就會出現這個問題,線程A更改了緩存A中的數據,但是緩存B中的數據還是原來的數據,那麼線程B去緩存B中讀取的數據就是錯誤的數據。這個就是緩存一致性的問題了。(注:問題的產生是因爲多緩存引起的)

 

既然問題的產生是因爲多緩存引起的,那麼爲什麼不讓所有的處理器共享一個緩存呢?那麼在一個指令週期內只有一個處理器能夠通過一級緩存運行它的指令。這樣效率實在是太低了。所以就有了緩存一致性協議。

緩存一致性協議有多種,但是你日常處理的大多數計算機設備使用的都屬於“窺探(snooping)”協議,窺探”背後的基本思想是,所有內存傳輸都發生在一條共享的總線上,而所有的處理器都能看到這條總線:緩存本身是獨立的,但是內存是共享資源,所有的內存訪問都要經過仲裁(arbitrate):同一個指令週期中,只有一個緩存可以讀寫內存。窺探協議的思想是,緩存不僅僅在做內存傳輸的時候才和總線打交道,而是不停地在窺探總線上發生的數據交換,跟蹤其他緩存在做什麼。所以當一個緩存代表它所屬的處理器去讀寫內存時,其他處理器都會得到通知,它們以此來使自己的緩存保持同步。只要某個處理器一寫內存,其他處理器馬上就知道這塊內存在它們自己的緩存中對應的段已經失效。

在直寫模式下,這是很直接的,因爲寫操作一旦發生,它的效果馬上會被“公佈”出去。但是如果混着回寫模式,就有問題了。因爲有可能在寫指令執行過後很久,數據纔會被真正回寫到物理內存中——在這段時間內,其他處理器的緩存也可能會傻乎乎地去寫同一塊內存地址,導致衝突。在回寫模型中,簡單把內存寫操作的信息廣播給其他處理器是不夠的,我們需要做的是,在修改本地緩存之前,就要告知其他處理器。搞懂了細節,就找到了處理回寫模式這個問題的最簡單方案,我們通常叫做MESI協議(譯者注:MESI是Modified、Exclusive、Shared、Invalid的首字母縮寫,代表四種緩存狀態,下面的譯文中可能會以單個字母指代相應的狀態)。

其中的Exclusive表示獨佔緩存段,當處理器想寫某個緩存段時,如果它沒有獨佔權,它必須先發送一條“我要獨佔權”的請求給總線,這會通知其他處理器,把它們擁有的同一緩存段的拷貝失效(如果它們有的話)。只有在獲得獨佔權後,處理器才能開始修改數據——並且此時,這個處理器知道,這個緩存段只有一份拷貝,在我自己的緩存裏,所以不會有任何衝突。


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