讀懂作業系統之快取原理(cache)(三)

Jeffcky發表於2020-06-07

前言

本節內容計劃是講解TLB與快取記憶體的關係,但是在涉及高速緩的前提是我們必須要了解作業系統快取原理,所以提前先詳細瞭解下快取原理,我們依然是採取循序漸進的方式來解答快取原理,若有敘述不當之處,還請批評指正。

快取原理

快取記憶體被劃分為多個塊,其大小可能不同,快取中的塊數通常為2的冪。如下為一個具有八個塊的快取記憶體,每個塊包含一個位元組。

通過本節對快取原理的學習我們能夠學習到四點:

【1】當我們將資料塊從主儲存器複製到快取,我們到底應該放在哪裡?

【2】如何判斷一個字是否已經在快取中,或者它是否必須首先從主儲存器中獲取?

【3】較小的快取最終會填滿, 需要至從主存載入新塊,我們必須替換快取中現有的哪個塊?

【4】儲存系統如何處理寫操作?

資料放在快取哪裡?

快取最簡單的資料結構是直接對映: 其中每個儲存器地址僅僅對應到快取中的一個位置。例如如下,16個位元組的主存和4個位元組的快取(每個塊一個位元組),記憶體地址為0、4、8、12分別對映到快取中為0的塊,而地址1、5、9、13被對映到塊1

等等,我們是不是講解的太快了,上述地址怎麼就劃分到比如塊0或塊1了呢?要找出快取所在塊採取取模法:(塊地址)mod (快取中的塊數),如果快取包含2k塊,則記憶體地址i處的資料將進入快取塊索引為i mod 2k。還是不懂?我們來舉個例子,如下快取有4個塊,那麼地址為14將對映到塊2即(14 mod 4 = 2)。

為便於大家理解如上為10進製表示記憶體地址,將記憶體地址對映到快取塊中實際等效的方式是將記憶體地址中的最低有效k位(二進位制)進行對映。正如下面我們所看到的,記憶體地址14(1110,二進位制)將最低有效位10作為塊中的索引

怎樣找到快取中資料?

到目前為止我們知道了將地址利用直接對映的結構對映到快取中,那麼我們找到資料是否在快取中呢?如果要讀取記憶體地址i,則可以使用mod技巧來確定哪個快取塊將包含i,如上所述,若其他地址也可能對映到相同的快取塊,那麼我們如何區分它們呢?例如如下記憶體地址2、6、10、14都在快取塊2中

為了解決這個問題,我們需要向快取記憶體中新增標記(tag),通過記憶體地址的高位來提供標記位,以使我們能夠區分對映到同一快取記憶體塊的不同儲存位置。例如如下。記憶體地址6即(0110,二進位制),將低位10作為索引(index),高位01作為標記(tag)。

我們通過將快取記憶體塊標記(tag)與塊索引(index)組合起來,可以準確地知道主儲存器的哪些地址儲存在快取記憶體中。

當程式載入到記憶體中時,快取為空,不包含有效資料,我們應該通過為每個快取塊新增一個有效位來解決這個問題,系統初始化時,所有有效位均設定為0,當資料載入到特定的快取塊中時,相應的有效位設定為1。

當CPU嘗試從記憶體中讀取資料時,該地址將被髮送到快取控制器,地址的最低k位將在快取中索引一個塊,如果該塊有效且標籤與m位地址的高(m-k)位匹配,則該資料將被髮送到CPU,如下為一個32位記憶體地址和210位元組快取記憶體的圖。

到這裡我們會發現一個問題,將每一個位元組對應一位元組快取塊並沒有很好的利用空間區域性性,要是訪問一個地址後將訪問附近的地址,我們又該怎麼辦?我們要做的是將快取塊的大小要大於1個位元組。如下,我們使用兩個位元組的塊,因此我們可以用兩個來載入快取一次讀取一個位元組,如果我們從記憶體地址12讀取資料,則地址中的資料12和13都將被複制到快取塊2。

現在,我們又該如何確定資料應放在快取中的位置?現在演變成塊地址,如果快取塊大小為2n位元組,我們也可以在概念上將主記憶體也劃分成2n位元組塊,要確定位元組地址i的塊地址,可以進行整數除法(i / 2n),如下示例中有2個位元組的快取塊,因此我們可以將16個位元組的主儲存器視為8塊主儲存器,例如,儲存器地址12和13都對應於塊地址6,因為12 / 2 = 6和13 / 2 = 6。 

現在我們知道了塊地址,就可以像上述一樣將其對映到快取:找到塊地址除以快取塊數後的餘數。在如下示例中,記憶體塊6屬於快取塊2,因為6 mod 4 =2,這對應於將來自儲存器位元組地址12和13的資料都放入快取記憶體塊2中。

當我們訪問記憶體中的一個位元組資料時,我們會將其整個塊複製到快取中以達到充分利用空間區域性性。在我們的示例中,如果程式從位元組地址12讀取,我們會將所有儲存塊6(地址12和13)都載入到快取塊2中(注意:位元組地址13對應於相同的儲存塊地址)因此,對地址13的讀取也會導致將儲存塊6(地址12和13)載入到快取記憶體塊2中。為了簡化起見,儲存塊的位元組i始終儲存在相應快取記憶體塊的位元組i中。

假設我們有一個包含2k塊的快取,每個塊包含2n個位元組,我們可以通過檢視其在主記憶體中的地址來確定該快取中一個位元組的資料位置,地址的k位將選擇2k個快取記憶體塊之一,最低的n位現在是一個塊偏移量,它決定了快取記憶體塊中的2n個位元組中的哪個將儲存資料。

我們來舉個例子加深理解,如下示例使用22塊快取記憶體,每個塊佔21位元組,因此,儲存器地址13(1101)將儲存在快取記憶體塊2的位元組1中。

到這裡為止,我們才算分析清楚了快取中有效位、標記位、索引、偏移它們的由來以及實際作用。同時對於快取採用的直接對映(direct mapped)結構:索引和偏移量可以使用位運算子或簡單的算術運算,因為每個記憶體地址都恰好屬於一個塊。實際上我們可以將一個塊放置到快取中的任何一個位置,這種機制稱為全相聯(fully associative)。全相聯的快取記憶體允許將資料儲存在任何快取記憶體塊中,而不是將每個記憶體地址強制對映到一個特定的塊中,從記憶體中獲取資料時,可以將其放置在快取記憶體的任何未使用塊中。 這樣,我們將永遠不會在對映到單個快取塊的兩個或多個記憶體地址之間發生衝突,在上述示例中,我們可能將記憶體地址2放在快取塊2中,並將地址6放在塊3中。然後對2和6的後續重複訪問將全部命中而不是未命中,如果所有塊都已被使用,則使用LRU演算法進行替換。但是在全相聯快取中要查詢一個指定的塊,由於該塊存放在快取中的任何位置,因此需要檢索快取中的所有項,為了是檢索更加有效,它是由一個與快取中每個項都相關的比較器並行完成的,這些比較器加大了硬體開銷,因而,全相聯只適合塊數較少的快取。介於直接對映和全相聯之間的設計是組相聯(set associative)。在組相聯快取中,每個塊可被放置的位置數固定,每個塊有n個位置可放的快取被稱作n路組相聯,一個n路組相聯快取由很多組組成,每個組有n個塊。通過上述對直接對映的講解,最終我們得出指定記憶體地址所在儲存的塊號為:(塊號) mod (快取中的塊數),而組相聯對於儲存塊號是:(塊號) mod (快取中的組數)。如下為8塊快取記憶體的組織

組相聯實際上就是將塊進行分組,比如如上第一個圖則是直接對映(我們大可將其看做是每一個塊就是一個組,所以是1路8組相聯),而第二張圖則是每2個塊作為一組,所以是2路4組相聯,同理第三張圖是4路2組相聯。換句話說,若每組有2n塊,那麼就是2n路相聯。通過對組相聯的講解,我們再敘記憶體地址在組相聯快取中的位置。如果我們有2s組並且每塊有2n位元組,那麼記憶體地址對映在快取中的位置則是如下這般

現在我們運算則是計算快取中的組索引而非再是塊,上述Block offset(在組中塊偏移)= 記憶體地址 mod 2n,塊地址 = 記憶體地址 / 2n,set index(組索引) = 塊地址 mod 2s。我們還是通過圖解來進行敘述,假設有一個8塊的快取記憶體,然後每個塊是16個位元組,那麼記憶體地址為6195的資料儲存在快取哪裡呢?首先我們將6195轉換為二進位制  = 110000 011 0011,因每個塊是16位元組即24,所以塊偏移量為4位即0011,若採用1路8組相聯(直接對映)那麼其組索引就是(6195 mod 8) = 3,取6195轉換而二進位制去除偏移量4位,所以為011,同理(根據上述給定計算公式)對於2路4組相聯其組索引為(11),4路2組相聯其組索引為(1),如下:

到這裡我們知道將資料進行快取我們可以採取直接對映、組相聯、全相聯的機制,通過增加相聯度通常可以降低快取缺失率,但是增加相聯度也就增加了每組中的塊數,也就是並行查詢時同時比較的次數,相聯度每增加兩倍就會使得每組中的塊數加倍而使得組數減半,所以增大了訪問時間的開銷。如何找到一個塊,當然也就依賴於所使用的將塊放置的機制(直接對映、組相聯、全相聯),相較於全相聯而言,它使用複雜的替換策略而降低缺失率且很容易被索引,而不需要額外的硬體,也不需要進行查詢。因此虛擬儲存系統通常使用全相聯對映,而組相聯對映通常應用於快取和TLB。

快取缺失和寫操作

快取缺失被分為以下三類(3C模型,three Cs model),因其三類名稱以字母c開頭而得名

強制缺失(compulsory miss):對從沒有在快取中出現的塊第一次進行訪問引起的缺失,也稱為冷啟動缺失(cold-start miss)

容量缺失:(capacity miss):由於快取容納不了一個程式執行所需要的所有塊而引起的快取缺失,當某些塊被替換出去,隨後再被調入時,將發生容量缺失

衝突缺失(conflict miss):在組相聯或者直接對映的快取中,多個競爭同一個組時而引起的快取缺失。衝突缺失在直接對映或組相聯快取中存在,而在同樣大小的全相聯快取中不存在,這種快取缺失也稱為碰撞缺失(collision miss)

改變快取設計的某一方面就能直接影響這些缺失的原因。衝突缺失是因為爭用同一個快取塊而引起的,因此提高相聯度可以減少衝突缺失,然後提高相聯度會延長訪問時間,導致整個效能的降低,容量缺失可以簡單地通過增大快取容量來減少,當然快取容量增大的同時必然導致訪問時間的增加,也將導致整體效能的降低。

在相聯的快取中發生缺失時,我們必須決定替換哪一塊,如若是全相聯,那麼所有的塊都是被替換的候選者,如若是組相聯,我們必須在某一組的塊中進行選擇,當然,直接對映的快取替換很簡單,因為只有一個可以替換的候選者。因此在全相聯或組相聯快取中 ,有兩種主要的替換策略

隨機法:隨機選擇候選塊,可能使用一些硬體來協助實現,例如TLB缺失、MIPS支援隨機替換

LRU(最近最少使用演算法):被替換的塊是最久沒有被使用過的塊 (在大多虛擬儲存器中,對於LRU都是通過提供引用位來近似實現(比如TLB))

指令快取缺失(資料缺失也類似如此)處理步驟如下:

【1】將程式計數器(PC)的原始值送到暫存器

【2】通知主存執行一次讀操作,並等待主存訪問完成

【3】寫快取項,將從主存取回的資料寫入快取中存放資料的部分,並將高位(從ALU中得到)寫入標記域,設定有效位

【4】重啟指令執行第一步,重新取指,這次該指令發生在快取中

資料訪問是對快取的控制基本相同:發生缺失時,處理器發生阻塞,直到從儲存器中取回資料後才響應。在執行寫操作時,如果有一個儲存指令,我們只將資料寫入快取而不改變主存中的內容,那麼在寫入快取後將導致快取和主存被認為不一致,保持主存和快取一致性最簡單的方法是將資料同時寫入主存和快取中,這種方法稱為【寫直達】法。但是這種方法無法提供良好的效能,因為每次寫操作都要把資料寫入主存中,這些寫操作將花費大量的時間,可能至少花費100個處理時鐘週期,並且大大降低了機器速度,解決這個問題的方案之一是採用【寫緩衝:一個儲存等待寫入主存資料的緩衝佇列】,當一個資料在等待寫入快取時,先將其寫入緩衝中,當資料寫入快取和緩衝後,處理器可以繼續執行,當寫主存操作完成後,寫緩衝裡的資料項也得到有效釋放。如果寫緩衝已經滿了,那麼當處理器執行到一個寫操作時就必須停下來直到寫緩衝中有一個空位置,當然,如果儲存器完成寫操作的速度比處理器產生寫操作的速度慢,那麼再多的緩衝器也無用,因為產生寫操作比儲存系統接收它們更快。

 

除了寫直達方法外,另外一種可選擇的方法是【寫回】,在寫回機制中,當發生寫操作時,新值僅僅被寫入到快取塊中,只有當修改過的塊被替換時才需要寫到磁碟上,寫回機制可提高系統效能,尤其是當處理器寫操作的速度和主存處理寫操作速度一樣快甚至更快時,但是,寫回機制的實現比寫直達要複雜得多。大部分寫回機制的緩衝也是使用寫緩衝,當在發生缺失替換一個被修改的塊時,寫緩衝可以起到降低缺失代價的作用。在這種情況下,被修改的資料塊移入與快取相聯的寫回緩衝器,同時從主存中讀出所需要的資料塊。隨後,寫回緩衝器再將資料寫入主存,如果下一次缺失沒有立刻發生,當髒資料塊必須被替換時,這種方法可以減少一半的缺失代價。

總結

一個快取塊可以放在何處:一個位置(直接對映),一些位置(組相聯),任何位置(全相聯)。

如何找到一個塊:索引(直接對映的快取中),有限的檢索(組相聯的快取中),全部檢索(全相聯的快取中)、專用查詢頁表。

快取缺失時替換哪一塊:隨機選取、LRU

寫操作如何處理:寫直達或寫回策略

本文我們非常詳細的講解了快取的基本原理,當然對於如何處理快取一致性並未涉及(大多采用監聽協議),希望通過我對快取原理的理解能給閱讀的您能有力所能及的幫助,謝謝。 

相關文章