1. Page Cache
1.1 Page Cache 是什么?
為了理解 Page Cache,我們不妨先看一下 Linux 的檔案 I/O 系統,如下圖所示:

Figure1. Linux 檔案 I/O 系統
上圖中,紅色部分為 Page Cache,可見 Page Cache 的本質是由 Linux 內核管理的記憶體區域,我們通過 mmap 以及 buffered I/O 將檔案讀取到記憶體空間實際上都是讀取到 Page Cache 中,
1.2 如何查看系統的 Page Cache?
通過讀取 /proc/meminfo 檔案,能夠實時獲取系統記憶體情況:
$ cat /proc/meminfo
...
Buffers: 1224 kB
Cached: 111472 kB
SwapCached: 36364 kB
Active: 6224232 kB
Inactive: 979432 kB
Active(anon): 6173036 kB
Inactive(anon): 927932 kB
Active(file): 51196 kB
Inactive(file): 51500 kB
...
Shmem: 10000 kB
...
SReclaimable: 43532 kB
...
根據上面的資料,你可以簡單得出這樣的公式(等式兩邊之和都是 112696 KB):
Buffers + Cached + SwapCached = Active(file) + Inactive(file) + Shmem + SwapCached
兩邊等式都是 Page Cache,即:
Page Cache = Buffers + Cached + SwapCached
通過閱讀 1.4 以及 1.5 小節,就能夠理解為什么 SwapCached 與 Buffers 也是 Page Cache 的一部分,
1.3 page 與 Page Cache
page 是記憶體管理分配的基本單位, Page Cache 由多個 page 構成,page 在作業系統中通常為 4KB 大小(32bits/64bits),而 Page Cache 的大小則為 4KB 的整數倍,
另一方面,并不是所有 page 都被組織為 Page Cache,
Linux 系統上供用戶可訪問的記憶體分為兩個型別[2],即:
- File-backed pages:檔案備份頁也就是 Page Cache 中的 page,對應于磁盤上的若干資料塊;對于這些頁最大的問題是臟頁回盤;
- Anonymous pages:匿名頁不對應磁盤上的任何磁盤資料塊,它們是行程的運行是記憶體空間(例如方法堆疊、區域變數表等屬性);
為什么 Linux 不把 Page Cache 稱為 block cache,這不是更好嗎?
這是因為從磁盤中加載到記憶體的資料不僅僅放在 Page Cache 中,還放在 buffer cache 中,例如通過 Direct I/O 技術的磁盤檔案就不會進入 Page Cache 中,當然,這個問題也有 Linux 歷史設計的原因,畢竟這只是一個稱呼,含義隨著 Linux 系統的演進也逐漸不同,
下面比較一下 File-backed pages 與 Anonymous pages 在 Swap 機制下的性能,
記憶體是一種珍惜資源,當記憶體不夠用時,記憶體管理單元(Memory Mangament Unit)需要提供調度演算法來回收相關記憶體空間,記憶體空間回收的方式通常就是 swap,即交換到持久化存盤設備上,
File-backed pages(Page Cache)的記憶體回收代價較低, Page Cache 通常對應于一個檔案上的若干順序塊,因此可以通過順序 I/O 的方式落盤,另一方面,如果 Page Cache 上沒有進行寫操作(所謂的沒有臟頁),甚至不會將 Page Cache 回盤,因為資料的內容完全可以通過再次讀取磁盤檔案得到,
Page Cache 的主要難點在于臟頁回盤,這個內容會在第二節進行詳細說明,
Anonymous pages 的記憶體回收代價較高,這是因為 Anonymous pages 通常隨機地寫入持久化交換設備,另一方面,無論是否有寫操作,為了確保資料不丟失,Anonymous pages 在 swap 時必須持久化到磁盤,
1.4 Swap 與缺頁中斷
Swap 機制指的是當物理記憶體不夠用,記憶體管理單元(Memory Mangament Unit,MMU)需要提供調度演算法來回收相關記憶體空間,然后將清理出來的記憶體空間給當前記憶體申請方,
Swap 機制存在的本質原因是 Linux 系統提供了虛擬記憶體管理機制,每一個行程認為其獨占記憶體空間,因此所有行程的記憶體空間之和遠遠大于物理記憶體,所有行程的記憶體空間之和超過物理記憶體的部分就需要交換到磁盤上,
作業系統以 page 為單位管理記憶體,當行程發現需要訪問的資料不在記憶體時,作業系統可能會將資料以頁的方式加載到記憶體中,上述程序被稱為缺頁中斷,當作業系統發生缺頁中斷時,就會通過系統呼叫將 page 再次讀到記憶體中,
但主記憶體的空間是有限的,當主記憶體中不包含可以使用的空間時,作業系統會從選擇合適的物理記憶體頁驅逐回磁盤,為新的記憶體頁讓出位置,選擇待驅逐頁的程序在作業系統中叫做頁面替換(Page Replacement),替換操作又會觸發 swap 機制,
如果物理記憶體足夠大,那么可能不需要 Swap 機制,但是 Swap 在這種情況下還是有一定優勢:對于有發生記憶體泄漏幾率的應用程式(行程),Swap 交換磁區更是重要,這可以確保記憶體泄露不至于導致物理記憶體不夠用,最終導致系統崩潰,但記憶體泄露會引起頻繁的 swap,此時非常影響作業系統的性能,
Linux 通過一個 swappiness 引數來控制 Swap 機制[2]:這個引數值可為 0-100,控制系統 swap 的優先級:
- 高數值:較高頻率的 swap,行程不活躍時主動將其轉換出物理記憶體,
- 低數值:較低頻率的 swap,這可以確保互動式不因為記憶體空間頻繁地交換到磁盤而提高回應延遲,
最后,為什么 Buffers 也是 Page Cache 的一部分?
這是因為當匿名頁(Inactive(anon) 以及 Active(anon))先被交換(swap out)到磁盤上后,然后再加載回(swap in)記憶體中,由于讀入到記憶體后原來的 Swap File 還在,所以 SwapCached 也可以認為是 File-backed page,即屬于 Page Cache,這個程序如 Figure 2 所示,

Figure2. 匿名頁的被交換后也是 Page Cache
1.5 Page Cache 與 buffer cache
執行 free 命令,注意到會有兩列名為 buffers 和 cached,也有一行名為 “-/+ buffers/cache”,
~ free -m
total used free shared buffers cached
Mem: 128956 96440 32515 0 5368 39900
-/+ buffers/cache: 51172 77784
Swap: 16002 0 16001
其中,cached 串列示當前的頁快取(Page Cache)占用量,buffers 串列示當前的塊快取(buffer cache)占用量,用一句話來解釋:Page Cache 用于快取檔案的頁資料,buffer cache 用于快取塊設備(如磁盤)的塊資料,頁是邏輯上的概念,因此 Page Cache 是與檔案系統同級的;塊是物理上的概念,因此 buffer cache 是與塊設備驅動程式同級的,
其中,cached 串列示當前的頁快取(Page Cache)占用量,buffers 串列示當前的塊快取(buffer cache)占用量,用一句話來解釋:Page Cache 用于快取檔案的頁資料,buffer cache 用于快取塊設備(如磁盤)的塊資料,頁是邏輯上的概念,因此 Page Cache 是與檔案系統同級的;塊是物理上的概念,因此 buffer cache 是與塊設備驅動程式同級的,
Page Cache 與 buffer cache 的共同目的都是加速資料 I/O:寫資料時首先寫到快取,將寫入的頁標記為 dirty,然后向外部存盤 flush,也就是快取寫機制中的 write-back(另一種是 write-through,Linux 默認情況下不采用);讀資料時首先讀取快取,如果未命中,再去外部存盤讀取,并且將讀取來的資料也加入快取,作業系統總是積極地將所有空閑記憶體都用作 Page Cache 和 buffer cache,當記憶體不夠用時也會用 LRU 等演算法淘汰快取頁,
在 Linux 2.4 版本的內核之前,Page Cache 與 buffer cache 是完全分離的,但是,塊設備大多是磁盤,磁盤上的資料又大多通過檔案系統來組織,這種設計導致很多資料被快取了兩次,浪費記憶體,所以在 2.4 版本內核之后,兩塊快取近似融合在了一起:如果一個檔案的頁加載到了 Page Cache,那么同時 buffer cache 只需要維護塊指向頁的指標就可以了,只有那些沒有檔案表示的塊,或者繞過了檔案系統直接操作(如dd命令)的塊,才會真正放到 buffer cache 里,因此,我們現在提起 Page Cache,基本上都同時指 Page Cache 和 buffer cache 兩者,本文之后也不再區分,直接統稱為 Page Cache,
下圖近似地示出 32-bit Linux 系統中可能的一種 Page Cache 結構,其中 block size 大小為 1KB,page size 大小為 4KB,

Page Cache 中的每個檔案都是一棵基數樹(radix tree,本質上是多叉搜索樹),樹的每個節點都是一個頁,根據檔案內的偏移量就可以快速定位到所在的頁,如下圖所示,關于基數樹的原理可以參見英文維基,這里就不細說了,

1.6 Page Cache 與預讀
作業系統為基于 Page Cache 的讀快取機制提供預讀機制(PAGE_READAHEAD),一個例子是:
- 用戶執行緒僅僅請求讀取磁盤上檔案 A 的 offset 為 0-3KB 范圍內的資料,由于磁盤的基本讀寫單位為 block(4KB),于是作業系統至少會讀 0-4KB 的內容,這恰好可以在一個 page 中裝下,
- 但是作業系統出于區域性原理[3]會選擇將磁盤塊 offset [4KB,8KB)、[8KB,12KB) 以及 [12KB,16KB) 都加載到記憶體,于是額外在記憶體中申請了 3 個 page;
下圖代表了作業系統的預讀機制:

Figure.作業系統的預讀機制;
上圖中,應用程式利用 read 系統調動讀取 4KB 資料,實際上內核使用 readahead 機制完成了 16KB 資料的讀取,
2. Page Cache 與檔案持久化的一致性&可靠性
現代 Linux 的 Page Cache 正如其名,是對磁盤上 page(頁)的記憶體快取,同時可以用于讀/寫操作,任何系統引入快取,就會引發一致性問題:記憶體中的資料與磁盤中的資料不一致,例如常見后端架構中的 Redis 快取與 MySQL 資料庫就存在一致性問題,
Linux 提供多種機制來保證資料一致性,但無論是單機上的記憶體與磁盤一致性,還是分布式組件中節點 1 與節點 2 、節點 3 的資料一致性問題,理解的關鍵是 trade-off:吞吐量與資料一致性保證是一對矛盾,
首先,需要我們理解一下檔案的資料,檔案 = 資料 + 元資料,元資料用來描述檔案的各種屬性,也必須存盤在磁盤上,因此,我們說保證檔案一致性其實包含了兩個方面:資料一致+元資料一致,
檔案的元資料包括:檔案大小、創建時間、訪問時間、屬主屬組等資訊,
我們考慮如下一致性問題:如果發生寫操作并且對應的資料在 Page Cache 中,那么寫操作就會直接作用于 Page Cache 中,此時如果資料還沒重繪到磁盤,那么記憶體中的資料就領先于磁盤,此時對應 page 就被稱為 Dirty page,
當前 Linux 下以兩種方式實作檔案一致性:
- Write Through(寫穿):向用戶層提供特定介面,應用程式可主動呼叫介面來保證檔案一致性;
- Write back(寫回):系統中存在定期任務(表現形式為內核執行緒),周期性地同步檔案系統中檔案臟資料塊,這是默認的 Linux 一致性方案;
上述兩種方式最終都依賴于系統呼叫,主要分為如下三種系統呼叫:
| 方法 | 含義 |
|---|---|
| fsync(intfd) | fsync(fd):將 fd 代表的檔案的臟資料和臟元資料全部重繪至磁盤中, |
| fdatasync(int fd) | fdatasync(fd):將 fd 代表的檔案的臟資料重繪至磁盤,同時對必要的元資料重繪至磁盤中,這里所說的必要的概念是指:對接下來訪問檔案有關鍵作用的資訊,如檔案大小,而檔案修改時間等不屬于必要資訊 |
| sync() | sync():則是對系統中所有的臟的檔案資料元資料重繪至磁盤中 |
上述三種系統呼叫可以分別由用戶行程與內核行程發起,下面我們研究一下內核執行緒的相關特性,
- 創建的針對回寫任務的內核執行緒數由系統中持久存盤設備決定,為每個存盤設備創建單獨的重繪執行緒;
- 關于多執行緒的架構問題,Linux 內核采取了 Lighthttp 的做法,即系統中存在一個管理執行緒和多個重繪執行緒(每個持久存盤設備對應一個重繪執行緒),管理執行緒監控設備上的臟頁面情況,若設備一段時間內沒有產生臟頁面,就銷毀設備上的重繪執行緒;若監測到設備上有臟頁面需要回寫且尚未為該設備創建重繪執行緒,那么創建重繪執行緒處理臟頁面回寫,而重繪執行緒的任務較為單調,只負責將設備中的臟頁面回寫至持久存盤設備中,
- 重繪執行緒重繪設備上臟頁面大致設計如下:
- 每個設備保存臟檔案鏈表,保存的是該設備上存盤的臟檔案的 inode 節點,所謂的回寫檔案臟頁面即回寫該 inode 鏈表上的某些檔案的臟頁面;
- 系統中存在多個回寫時機,第一是應用程式主動呼叫回寫介面(fsync,fdatasync 以及 sync 等),第二管理執行緒周期性地喚醒設備上的回寫執行緒進行回寫,第三是某些應用程式/內核任務發現記憶體不足時要回收部分快取頁面而事先進行臟頁面回寫,設計一個統一的框架來管理這些回寫任務非常有必要,
Write Through 與 Write back 在持久化的可靠性上有所不同:
- Write Through 以犧牲系統 I/O 吞吐量作為代價,向上層應用確保一旦寫入,資料就已經落盤,不會丟失;
- Write back 在系統發生宕機的情況下無法確保資料已經落盤,因此存在資料丟失的問題,不過,在程式掛了,例如被 kill -9,Page Cache 中的資料作業系統還是會確保落盤;
3. Page Cache 的優劣勢
3.1 Page Cache 的優勢
1.加快資料訪問
如果資料能夠在記憶體中進行快取,那么下一次訪問就不需要通過磁盤 I/O 了,直接命中記憶體快取即可,
由于記憶體訪問比磁盤訪問快很多,因此加快資料訪問是 Page Cache 的一大優勢,
2.減少 I/O 次數,提高系統磁盤 I/O 吞吐量
得益于 Page Cache 的快取以及預讀能力,而程式又往往符合區域性原理,因此通過一次 I/O 將多個 page 裝入 Page Cache 能夠減少磁盤 I/O 次數, 進而提高系統磁盤 I/O 吞吐量,
3.2 Page Cache 的劣勢
page cache 也有其劣勢,最直接的缺點是需要占用額外物理記憶體空間,物理記憶體在比較緊俏的時候可能會導致頻繁的 swap 操作,最終導致系統的磁盤 I/O 負載的上升,
Page Cache 的另一個缺陷是對應用層并沒有提供很好的管理 API,幾乎是透明管理,應用層即使想優化 Page Cache 的使用策略也很難進行,因此一些應用選擇在用戶空間實作自己的 page 管理,而不使用 page cache,例如 MySQL InnoDB 存盤引擎以 16KB 的頁進行管理,
Page Cache 最后一個缺陷是在某些應用場景下比 Direct I/O 多一次磁盤讀 I/O 以及磁盤寫 I/O,這一點可以參考[4],
來源:https://spongecaptain.cool/SimpleClearFileIO/1. page cache.html
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502210.html
標籤:其他
上一篇:發現一個舔狗神器,Python真的太厲害了,自動下載妹子視頻...
下一篇:從XXE漏洞修復引起Not supported: http://javax.xml.XMLConstants/property/accessExternalDTD說到SPI機制
