1. 程式人生 > 程式設計 >Linux磁碟快取機制

Linux磁碟快取機制

前言

最近遇到了一起跟磁碟相關的線上故障,藉此總結一下之前不太瞭解的Linux磁碟快取相關的知識。

總的來說磁碟快取出現的原因大概有兩個:第一是訪問磁碟的速度遠慢於訪問記憶體的速度,通過在記憶體中快取磁碟內容可以提高訪問速度;第二是根據程式的區域性性原理,資料一旦被訪問過,就很有可能在短時間內再次被訪問,所以在記憶體中快取磁碟內容可以提高程式執行速度。

區域性性原理

程式區域性性原理:程式在執行時呈現出區域性性規律,即在一段時間內,整個程式的執行僅限於程式中的某一部分。相應地,執行所訪問的儲存空間也侷限於某個記憶體區域,具體來說,區域性性通常有兩種形式:時間區域性性和空間區域性性。

時間區域性性:被引用過一次的儲存器位置在未來會被多次引用。

空間區域性性:如果一個儲存器的位置被引用,那麼將來他附近的位置也會被引用。

頁快取

Linux系統中為了減少對磁碟的IO操作,會將開啟的磁碟內容進行快取,而快取的地方則是實體記憶體,進而將對磁碟的訪問轉換成對記憶體的訪問,有效提高程式的速度。Linux的快取方式是利用實體記憶體快取磁碟上的內容,稱為頁快取(page cache)。

頁快取是由記憶體中的物理頁面組成的,其內容對應磁碟上的物理塊。頁快取的大小會根據系統的記憶體空閒大小進行動態調整,它可以通過佔用記憶體以擴張大小,也可以自我收縮以緩解記憶體使用壓力。

在虛擬記憶體機製出現以前,作業系統使用塊快取系列,但是在虛擬記憶體出現以後,作業系統管理IO的粒度更大,因此採用了頁快取機制,頁快取是基於頁的、面向檔案的快取機制。

頁快取的讀取

Linux系統在讀取檔案時,會優先從頁快取中讀取檔案內容,如果頁快取不存在,系統會先從磁碟中讀取檔案內容更新到頁快取中,然後再從頁快取中讀取檔案內容並返回。大致過程如下:

  1. 程式呼叫庫函式read發起讀取檔案請求
  2. 核心檢查已開啟的檔案列表,呼叫檔案系統提供的read介面
  3. 找到檔案對應的inode,然後計算出要讀取的具體的頁
  4. 通過inode查詢對應的頁快取,1)如果頁快取節點命中,則直接返回檔案內容;2)如果沒有對應的頁快取,則會產生一個缺頁異常(page fault)。這時系統會建立新的空的頁快取並從磁碟中讀取檔案內容,更新頁快取,然後重複第4步
  5. 讀取檔案返回

所以說,所有的檔案內容的讀取,無論最初有沒有命中頁快取,最終都是直接來源於頁快取。

頁快取的寫入

因為頁快取的存在,當一個程式呼叫write時,對檔案的更新僅僅是被寫到了檔案的頁快取中,讓後將對應的頁標記為dirty,整個過程就結束了。Linux核心會在週期性地將髒頁寫回到磁碟,然後清理掉dirty標識。

由於寫操作只會把變更寫入頁快取,因此程式並不會因此為阻塞直到磁碟IO發生,如果此時計算機崩潰,寫操作的變更可能並沒有發生在磁碟上。所以對於一些要求比較嚴格的寫操作,比如資料系統,就需要主動呼叫fsync等操作及時將變更同步到磁碟上。讀操作則不同,read通常會阻塞直到程式讀取到資料,而為了減少讀操作的這種延遲,Linux系統還是用了“預讀”的技術,即從磁碟中讀取資料時,核心將會多讀取一些頁到頁快取中。

回寫執行緒

頁快取的回寫是由核心中的單獨的執行緒來完成的,回寫執行緒會在以下3種情況下進行回寫:

  1. 空閒記憶體低於閾值時。當空閒記憶體不足時,需要釋放掉一部分快取,由於只有不髒的頁才能被釋放,所以需要把髒頁都回寫到磁碟,使其變為可回收的乾淨的頁。
  2. 髒頁在記憶體中處理時間超過閾值時。這是為了確保髒頁不會無限期的留在記憶體中,減少資料丟失的風險。
  3. 當使用者程式呼叫sync和fsync系統呼叫時。這是為了給使用者程式提供強制回寫的方法,滿足回寫要求嚴格的使用場景。

回寫執行緒的實現

名稱 版本 說明
bdflush 2.6版本以前 bdflush 核心執行緒在後臺執行,系統中只有一個 bdflush 執行緒,當記憶體消耗到特定閥值以下時,bdflush 執行緒被喚醒。kupdated 週期性的執行,寫回髒頁。 但是整個系統僅僅只有一個 bdflush 執行緒,當系統回寫任務較重時,bdflush 執行緒可能會阻塞在某個磁碟的I/O上,導致其他磁碟的I/O回寫操作不能及時執行。
pdflush 2.6版本引入 pdflush 執行緒數目是動態的,取決於系統的I/O負載。它是面向系統中所有磁碟的全域性任務的。 但是由於 pdflush 是面向所有磁碟的,所以有可能出現多個 pdflush 執行緒全部阻塞在某個擁塞的磁碟上,同樣導致其他磁碟的I/O回寫不能及時執行。
flusher執行緒 2.6.32版本以後引入 flusher 執行緒的數目不是唯一的,同時flusher執行緒不是面向所有磁碟的,而是每個flusher執行緒對應一個磁碟

頁快取的回收

Linux中頁快取的替換邏輯是一個修改過的LRU實現,也稱為雙鏈策略。和以前不同,Linux維護的不再是一個LRU連結串列,而是維護兩個連結串列:活躍連結串列和非活躍連結串列。處於活躍連結串列上的頁面被認為是“熱”的且不會被換出,而在非活躍連結串列上的頁面則是可以被換出的。在活躍連結串列中的頁面必須在其被訪問時就處於非活躍連結串列中。兩個連結串列都被偽LRU規則維護:頁面從尾部加入,從頭部移除,如同佇列。兩個連結串列需要維持平衡–如果活躍連結串列變得過多而超過了非活躍連結串列,那麼活躍連結串列的頭頁面將被重新移回到非活躍連結串列中,一遍能再被回收。雙連結串列策略解決了傳統LRU演演算法中對僅一次訪問的窘境。而且也更加簡單的實現了偽LRU語義。這種雙連結串列方式也稱作LRU/2。更普遍的是n個連結串列,故稱LRU/n。

總結

在這次遇到的線上故障中,根本原因在於在業務邏輯中使用了臨時檔案做快取,一個臨時檔案建立後如果在短時間內刪除,這時候對這個檔案的操作都是在頁快取內進行,不會實際回寫到磁碟。當程式出現問題響應變慢時,臨時檔案存活時間變長,就可能會使其被回寫到磁碟上,導致磁碟壓力過大,進而影響整個系統。