深入理解 Linux 的 Page Cache
1. Page Cache
1.1 Page Cache 是什么?
為了理解 Page Cache,我們不妨先看一下 Linux 的文件 I/O 系統(tǒng),如下圖所示:Figure1. Linux 文件 I/O 系統(tǒng)上圖中,紅色部分為 Page Cache。可見 Page Cache 的本質(zhì)是由 Linux 內(nèi)核管理的內(nèi)存區(qū)域。我們通過 mmap 以及 buffered I/O 將文件讀取到內(nèi)存空間實(shí)際上都是讀取到 Page Cache 中。
1.2 如何查看系統(tǒng)的 Page Cache?
通過讀取?/proc/meminfo
?文件,能夠?qū)崟r(shí)獲取系統(tǒng)內(nèi)存情況:$ 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
...
根據(jù)上面的數(shù)據(jù),你可以簡單得出這樣的公式(等式兩邊之和都是 112696 KB):Buffers Cached SwapCached = Active(file) Inactive(file) Shmem SwapCached
兩邊等式都是 Page Cache,即:Page Cache = Buffers Cached SwapCached
通過閱讀 1.4 以及 1.5 小節(jié),就能夠理解為什么 SwapCached 與 Buffers 也是 Page Cache 的一部分。題外話,小伙伴答案:內(nèi)核計(jì)算源碼(linux 2.6.19):內(nèi)核算法:Cached ?= files - SwapCached - Buffers;Buffers Cached SwapCached = Active(file) Inactive(file) Shmem SwapCached公式推出來的Cached = Active(file) Inactive(file) Shmem - Buffers ;由此可見,這個(gè)Cached?并不等于Active(file) Inactive(file)?;這個(gè)cache包含很多 :
- 含有普通文件數(shù)據(jù)的頁‘;
- 含有目錄的頁;
- 含有直接從塊設(shè)備文件(跳過文件系統(tǒng))讀出的數(shù)據(jù)的頁;
- 含有用戶態(tài)進(jìn)程數(shù)據(jù)的頁;
- 屬于特殊文件系統(tǒng)文件的頁,如shm;
1.3 page 與 Page Cache
page 是內(nèi)存管理分配的基本單位, Page Cache 由多個(gè) page 構(gòu)成。page 在操作系統(tǒng)中通常為 4KB 大小(32bits/64bits),而 Page Cache 的大小則為 4KB 的整數(shù)倍。另一方面,并不是所有 page 都被組織為 Page Cache。Linux 系統(tǒng)上供用戶可訪問的內(nèi)存分為兩個(gè)類型[2],即:- File-backed pages:文件備份頁也就是 Page Cache 中的 page,對應(yīng)于磁盤上的若干數(shù)據(jù)塊;對于這些頁最大的問題是臟頁回盤;
- Anonymous pages:匿名頁不對應(yīng)磁盤上的任何磁盤數(shù)據(jù)塊,它們是進(jìn)程的運(yùn)行是內(nèi)存空間(例如方法棧、局部變量表等屬性);
為什么 Linux 不把 Page Cache 稱為 block cache,這不是更好嗎?這是因?yàn)閺拇疟P中加載到內(nèi)存的數(shù)據(jù)不僅僅放在 Page Cache 中,還放在 buffer cache 中。例如通過 Direct I/O 技術(shù)的磁盤文件就不會(huì)進(jìn)入 Page Cache 中。當(dāng)然,這個(gè)問題也有 Linux 歷史設(shè)計(jì)的原因,畢竟這只是一個(gè)稱呼,含義隨著 Linux 系統(tǒng)的演進(jìn)也逐漸不同。下面比較一下 File-backed pages 與 Anonymous pages 在 Swap 機(jī)制下的性能。內(nèi)存是一種珍惜資源,當(dāng)內(nèi)存不夠用時(shí),內(nèi)存管理單元(Memory Mangament Unit)需要提供調(diào)度算法來回收相關(guān)內(nèi)存空間。內(nèi)存空間回收的方式通常就是 swap,即交換到持久化存儲(chǔ)設(shè)備上。File-backed pages(Page Cache)的內(nèi)存回收代價(jià)較低。Page Cache 通常對應(yīng)于一個(gè)文件上的若干順序塊,因此可以通過順序 I/O 的方式落盤。另一方面,如果 Page Cache 上沒有進(jìn)行寫操作(所謂的沒有臟頁),甚至不會(huì)將 Page Cache 回盤,因?yàn)閿?shù)據(jù)的內(nèi)容完全可以通過再次讀取磁盤文件得到。Page Cache 的主要難點(diǎn)在于臟頁回盤,這個(gè)內(nèi)容會(huì)在第二節(jié)進(jìn)行詳細(xì)說明。Anonymous pages 的內(nèi)存回收代價(jià)較高。這是因?yàn)?Anonymous pages 通常隨機(jī)地寫入持久化交換設(shè)備。另一方面,無論是否有更操作,為了確保數(shù)據(jù)不丟失,Anonymous pages 在 swap 時(shí)必須持久化到磁盤。
1.4 Swap 與缺頁中斷
Swap 機(jī)制指的是當(dāng)物理內(nèi)存不夠用,內(nèi)存管理單元(Memory Mangament Unit)需要提供調(diào)度算法來回收相關(guān)內(nèi)存空間,然后將清理出來的內(nèi)存空間給當(dāng)前內(nèi)存申請方。Swap 機(jī)制存在的本質(zhì)原因是 Linux 系統(tǒng)提供了虛擬內(nèi)存管理機(jī)制,每一個(gè)進(jìn)程認(rèn)為其獨(dú)占內(nèi)存空間,因此所有進(jìn)程的內(nèi)存空間之和遠(yuǎn)遠(yuǎn)大于物理內(nèi)存。所有進(jìn)程的內(nèi)存空間之和超過物理內(nèi)存的部分就需要交換到磁盤上。操作系統(tǒng)以 page 為單位管理內(nèi)存,當(dāng)進(jìn)程發(fā)現(xiàn)需要訪問的數(shù)據(jù)不在內(nèi)存時(shí),操作系統(tǒng)可能會(huì)將數(shù)據(jù)以頁的方式加載到內(nèi)存中。上述過程被稱為缺頁中斷,當(dāng)操作系統(tǒng)發(fā)生缺頁中斷時(shí),就會(huì)通過系統(tǒng)調(diào)用將 page 再次讀到內(nèi)存中。但主內(nèi)存的空間是有限的,當(dāng)主內(nèi)存中不包含可以使用的空間時(shí),操作系統(tǒng)會(huì)從選擇合適的物理內(nèi)存頁驅(qū)逐回磁盤,為新的內(nèi)存頁讓出位置,選擇待驅(qū)逐頁的過程在操作系統(tǒng)中叫做頁面替換(Page Replacement),替換操作又會(huì)觸發(fā) swap 機(jī)制。如果物理內(nèi)存足夠大,那么可能不需要 Swap 機(jī)制,但是 Swap 在這種情況下還是有一定優(yōu)勢:對于有發(fā)生內(nèi)存泄漏幾率的應(yīng)用程序(進(jìn)程),Swap 交換分區(qū)更是重要,這可以確保內(nèi)存泄露不至于導(dǎo)致物理內(nèi)存不夠用,最終導(dǎo)致系統(tǒng)崩潰。但內(nèi)存泄露會(huì)引起頻繁的 swap,此時(shí)非常影響操作系統(tǒng)的性能。Linux 通過一個(gè) swappiness 參數(shù)來控制 Swap 機(jī)制[2]:這個(gè)參數(shù)值可為 0-100,控制系統(tǒng) swap 的優(yōu)先級:- 高數(shù)值:較高頻率的 swap,進(jìn)程不活躍時(shí)主動(dòng)將其轉(zhuǎn)換出物理內(nèi)存。
- 低數(shù)值:較低頻率的 swap,這可以確保交互式不因?yàn)閮?nèi)存空間頻繁地交換到磁盤而提高響應(yīng)延遲。
Figure2. 匿名頁的被交換后也是 Page Cache
1.5 Page Cache 與 buffer cache
執(zhí)行 free 命令,注意到會(huì)有兩列名為 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 列表示當(dāng)前的頁緩存(Page Cache)占用量,buffers 列表示當(dāng)前的塊緩存(buffer cache)占用量。用一句話來解釋:Page Cache 用于緩存文件的頁數(shù)據(jù),buffer cache 用于緩存塊設(shè)備(如磁盤)的塊數(shù)據(jù)。頁是邏輯上的概念,因此 Page Cache 是與文件系統(tǒng)同級的;塊是物理上的概念,因此 buffer cache 是與塊設(shè)備驅(qū)動(dòng)程序同級的。Page Cache)占用量,buffers 列表示當(dāng)前的塊緩存(buffer cache)占用量。用一句話來解釋:Page Cache 用于緩存文件的頁數(shù)據(jù),buffer cache 用于緩存塊設(shè)備(如磁盤)的塊數(shù)據(jù)。頁是邏輯上的概念,因此 Page Cache 是與文件系統(tǒng)同級的;塊是物理上的概念,因此 buffer cache 是與塊設(shè)備驅(qū)動(dòng)程序同級的。Page Cache 與 buffer cache 的共同目的都是加速數(shù)據(jù) I/O:寫數(shù)據(jù)時(shí)首先寫到緩存,將寫入的頁標(biāo)記為 dirty,然后向外部存儲(chǔ) flush,也就是緩存寫機(jī)制中的 write-back(另一種是 write-through,Linux 默認(rèn)情況下不采用);讀數(shù)據(jù)時(shí)首先讀取緩存,如果未命中,再去外部存儲(chǔ)讀取,并且將讀取來的數(shù)據(jù)也加入緩存。操作系統(tǒng)總是積極地將所有空閑內(nèi)存都用作 Page Cache 和 buffer cache,當(dāng)內(nèi)存不夠用時(shí)也會(huì)用 LRU 等算法淘汰緩存頁。在 Linux 2.4 版本的內(nèi)核之前,Page Cache 與 buffer cache 是完全分離的。但是,塊設(shè)備大多是磁盤,磁盤上的數(shù)據(jù)又大多通過文件系統(tǒng)來組織,這種設(shè)計(jì)導(dǎo)致很多數(shù)據(jù)被緩存了兩次,浪費(fèi)內(nèi)存。所以在 2.4 版本內(nèi)核之后,兩塊緩存近似融合在了一起:如果一個(gè)文件的頁加載到了 Page Cache,那么同時(shí) buffer cache 只需要維護(hù)塊指向頁的指針就可以了。只有那些沒有文件表示的塊,或者繞過了文件系統(tǒng)直接操作(如dd命令)的塊,才會(huì)真正放到 buffer cache 里。因此,我們現(xiàn)在提起 Page Cache,基本上都同時(shí)指 Page Cache 和 buffer cache 兩者,本文之后也不再區(qū)分,直接統(tǒng)稱為 Page Cache。下圖近似地示出 32-bit Linux 系統(tǒng)中可能的一種 Page Cache 結(jié)構(gòu),其中 block size 大小為 1KB,page size 大小為 4KB。Page Cache 中的每個(gè)文件都是一棵基數(shù)樹(radix tree,本質(zhì)上是多叉搜索樹),樹的每個(gè)節(jié)點(diǎn)都是一個(gè)頁。根據(jù)文件內(nèi)的偏移量就可以快速定位到所在的頁,如下圖所示。關(guān)于基數(shù)樹的原理可以參見英文維基,這里就不細(xì)說了。
1.6 Page Cache 與預(yù)讀
操作系統(tǒng)為基于 Page Cache 的讀緩存機(jī)制提供預(yù)讀機(jī)制(PAGE_READAHEAD),一個(gè)例子是:- 用戶線程僅僅請求讀取磁盤上文件 A 的 offset 為 0-3KB 范圍內(nèi)的數(shù)據(jù),由于磁盤的基本讀寫單位為 block(4KB),于是操作系統(tǒng)至少會(huì)讀 0-4KB 的內(nèi)容,這恰好可以在一個(gè) page 中裝下。
- 但是操作系統(tǒng)出于局部性原理[3]會(huì)選擇將磁盤塊 offset [4KB,8KB)、[8KB,12KB) 以及 [12KB,16KB) 都加載到內(nèi)存,于是額外在內(nèi)存中申請了 3 個(gè) page;
下圖代表了操作系統(tǒng)的預(yù)讀機(jī)制:操作系統(tǒng)的預(yù)讀機(jī)制
上圖中,應(yīng)用程序利用 read 系統(tǒng)調(diào)動(dòng)讀取 4KB 數(shù)據(jù),實(shí)際上內(nèi)核使用 readahead 機(jī)制完成了 16KB 數(shù)據(jù)的讀取。