操做系統中的存儲器構成了一個金字塔,越往上的存儲器速度越快,可是價格也越貴,因此也就越小。爲了解決高速的處理器和低速的存儲器之間的矛盾,上一層的存儲器做爲下一層存儲器的緩存。html
在現代的CPU(大多數)上,全部的內存訪問都須要經過層層的緩存來進行。CPU的讀/寫(以及取指令)單元正常狀況下甚至都不能直接訪問內存——這是物理結構決定的;CPU都沒有管腳直接連到內存。相反,CPU和一級緩存(L1 Cache)通信,而一級緩存才能和內存通信。大約二十年前,一級緩存能夠直接和內存傳輸數據。現在,更多級別的緩存加入到設計中,一級緩存已經不能直接和內存通信了,它和二級緩存通信——而二級緩存才能和內存通信。或者還可能有三級緩存。你明白這個意思就行。linux
好比要須要操做內存的某個區域時,處理器不會直接去內存讀取,而是會去高速緩存中查看該區域是否是被調進來了,若是沒有,則把該區域調入高速緩衝區中。那麼接下來處理器直接在高速緩存中進行讀寫操做。緩存
一樣的,對於讀取磁盤數據,處理器把內存中的某塊區域做爲磁盤的緩存。那麼即可以直接在內存中進行讀寫。多線程
處理器在緩存中對數據進行讀寫操做,可是仍是須要把該數據寫會到原來的區域中去,這接涉及到了必定的策略。oop
(1)第一種策略稱爲不緩存(nowrite),也就是說高速緩存不去緩存任何寫操做。當對緩存中的數據進行寫時,將直接跳過緩存,直接寫到磁盤,同時標記緩存的數據失效。若是後續須要進行讀操做,須要從新從磁盤讀取數據。post
(2)第二種策略稱爲寫透緩存(write-through cache),即寫操做將自動更新緩存,同時也更新磁盤文件。這種操做對保持緩存一致性頗有好處,因此不須要將緩存標記爲失效,同時實現也比較簡單。spa
(3)第三種策略,也是linux所採用的,稱爲回寫(write-back)。在這種策略下,程序執行寫操做直接寫到緩存中,可是不會直接更新磁盤,而是將高速緩存中被寫入的頁面標記成「髒」,並加入髒頁鏈表中。而後由一個進程(回寫進程)週期性將髒頁鏈表的頁寫會到磁盤,從而讓磁盤中的數據和緩存中的數據一致,最後清理緩存的「髒」頁標誌。「髒」的意思不是說數據不乾淨,而是說數據沒有同步到磁盤。操作系統
如今的的多處理器的計算機,每一個CPU都有本身的寄存器和緩存。那麼一個多線程的程序就會出現這個問題,線程A更改了緩存A中的數據,可是緩存B中的數據仍是原來的數據,那麼線程B去緩存B中讀取的數據就是錯誤的數據。這個就是緩存一致性的問題了。(注:問題的產生是由於多緩存引發的)線程
既然問題的產生是由於多緩存引發的,那麼爲何不讓全部的處理器共享一個緩存呢?那麼在一個指令週期內只有一個處理器可以經過一級緩存運行它的指令。這樣效率實在是過低了。因此就有了緩存一致性協議。設計
緩存一致性協議有多種,可是你平常處理的大多數計算機設備使用的都屬於「窺探(snooping)」協議,窺探」背後的基本思想是,全部內存傳輸都發生在一條共享的總線上,而全部的處理器都能看到這條總線:緩存自己是獨立的,可是內存是共享資源,全部的內存訪問都要通過仲裁(arbitrate):同一個指令週期中,只有一個緩存能夠讀寫內存。窺探協議的思想是,緩存不只僅在作內存傳輸的時候才和總線打交道,而是不停地在窺探總線上發生的數據交換,跟蹤其餘緩存在作什麼。因此當一個緩存表明它所屬的處理器去讀寫內存時,其餘處理器都會獲得通知,它們以此來使本身的緩存保持同步。只要某個處理器一寫內存,其餘處理器立刻就知道這塊內存在它們本身的緩存中對應的段已經失效。
在直寫模式下,這是很直接的,由於寫操做一旦發生,它的效果立刻會被「公佈」出去。可是若是混着回寫模式,就有問題了。由於有可能在寫指令執行事後好久,數據纔會被真正回寫到物理內存中——在這段時間內,其餘處理器的緩存也可能會傻乎乎地去寫同一塊內存地址,致使衝突。在回寫模型中,簡單把內存寫操做的信息廣播給其餘處理器是不夠的,咱們須要作的是,在修改本地緩存以前,就要告知其餘處理器。搞懂了細節,就找到了處理回寫模式這個問題的最簡單方案,咱們一般叫作MESI協議(譯者注:MESI是Modified、Exclusive、Shared、Invalid的首字母縮寫,表明四種緩存狀態,下面的譯文中可能會以單個字母指代相應的狀態)。
其中的Exclusive表示獨佔緩存段,當處理器想寫某個緩存段時,若是它沒有獨佔權,它必須先發送一條「我要獨佔權」的請求給總線,這會通知其餘處理器,把它們擁有的同一緩存段的拷貝失效(若是它們有的話)。只有在得到獨佔權後,處理器才能開始修改數據——而且此時,這個處理器知道,這個緩存段只有一份拷貝,在我本身的緩存裏,因此不會有任何衝突。
該文章在個人我的博客的地址是:http://www.alphaway.org/post-432.html
參考: