如果說(shuō)想用一句話說(shuō)明白什么是 Page Cache,那么我感覺(jué)這么說(shuō)比較合適,“Page Cache 是用于加速對(duì)磁盤(pán)上數(shù)據(jù)訪問(wèn)的一部分 RAM”。下面是比較學(xué)院派的解釋。
Page Cache 是 Linux 內(nèi)核對(duì)磁盤(pán)最主要的緩存。大多數(shù)情況下,內(nèi)核在讀取或?qū)懭氪疟P(pán)時(shí)會(huì)引用 Page Cache。新頁(yè)面被添加到 Page Cache 中,以滿足用戶(hù)空間進(jìn)程的讀取請(qǐng)求。如果該頁(yè)尚未在緩存中,則會(huì)將新條目添加到緩存中并用從磁盤(pán)讀取的數(shù)據(jù)填充。該頁(yè)面將盡量久的保留在緩存中,然后可以由其他進(jìn)程重用,而無(wú)需訪問(wèn)磁盤(pán)。
類(lèi)似的,在向磁盤(pán)寫(xiě)入頁(yè)數(shù)據(jù)之前,內(nèi)核會(huì)驗(yàn)證相應(yīng)的頁(yè)面是否已經(jīng)包含在 Page Cache 中;如果不在,就會(huì)向緩存中添加一個(gè)新條目,并用要寫(xiě)入磁盤(pán)的數(shù)據(jù)填充。向磁盤(pán)寫(xiě)入的 I/O 數(shù)據(jù)傳輸不會(huì)立即開(kāi)始:磁盤(pán)更新會(huì)被延遲幾秒鐘,從而給進(jìn)程進(jìn)一步修改要寫(xiě)入的數(shù)據(jù)的機(jī)會(huì)(這里我感覺(jué)是為了批量處理更有效率)。

從圖中可以看到 Page Cache是內(nèi)核管理的內(nèi)存,也就是說(shuō),它屬于內(nèi)核而不屬于用戶(hù)。
在 Linux 中有多種方式可以查詢(xún) Page Cache,常用的有 free , /proc/meminfo,vmstat 等。
$free -m
total used free shared buff/cache available
Mem: 8192 3639 4058 34 493 4313
Swap: 0 0 0
$cat /proc/meminfo
MemTotal: 8388608 kB
MemFree: 4155612 kB
MemAvailable: 4417412 kB
Buffers: 0 kB
Cached: 506220 kB
SwapCached: 0 kB
Active: 169688 kB
Inactive: 4061128 kB
Active(anon): 528 kB
Inactive(anon): 3706692 kB
Active(file): 169160 kB
Inactive(file): 354436 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 3724116 kB
Mapped: 112992 kB
Shmem: 34848 kB
KReclaimable: 4820560 kB
Slab: 0 kB
SReclaimable: 0 kB
SUnreclaim: 0 kB
KernelStack: 0 kB
PageTables: 648124 kB
... ...
$vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 4154464 0 506748 0 0 1449 10175 2 2 0 0 100 0 0
Page Cache 的產(chǎn)生
從圖中還可以得到的信息就是 Page Cache 的產(chǎn)生主要有兩種方式:
- Buffered I/O(標(biāo)準(zhǔn)I/O)
- Memory-Mapped I/O(存儲(chǔ)映射I/O)
而直接 I/O (Direct I/O) 不會(huì)走 Page Cache 將直接與存儲(chǔ)打交道。

產(chǎn)生方式的區(qū)別
標(biāo)準(zhǔn) I/O 是先寫(xiě)到用戶(hù)緩沖區(qū)(User Space 對(duì)應(yīng)的內(nèi)存),然后再將用戶(hù)緩沖區(qū)里的數(shù)據(jù)拷貝(write(2))到內(nèi)核緩沖區(qū)(Page Cache Page對(duì)應(yīng)的內(nèi)存);如果是讀的(read(2))話,則是先從內(nèi)核緩沖區(qū)拷貝到用戶(hù)緩沖區(qū),再?gòu)挠脩?hù)緩沖區(qū)讀數(shù)據(jù),也就是 buffer 和文件內(nèi)容不存在任何映射關(guān)系。
對(duì)于存儲(chǔ)映射 I/O 而言,則是直接將 Page Cache Page 給映射到用戶(hù)地址空間,用戶(hù)直接讀寫(xiě)Page Cache Page 中內(nèi)容。
標(biāo)準(zhǔn)I/O

以標(biāo)準(zhǔn)I/O為例,解釋一下 Page Cache 是如何產(chǎn)生的。
- 首先往用戶(hù)緩沖區(qū)(buffer)中寫(xiě)入數(shù)據(jù),然后調(diào)用 write() 方法將 buffer 中的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)(Page Cache Page);
- 如果內(nèi)核緩沖區(qū)中沒(méi)有這個(gè) Page,就會(huì)發(fā)生 Page Fault(缺頁(yè)),將先分配一個(gè) Page;
- 然后執(zhí)行數(shù)據(jù)拷貝,該 Page Cache Page 就變成一個(gè) Dirty Page(臟頁(yè));
- 最后 Dirty Page 的內(nèi)容會(huì)同步到磁盤(pán),同步到磁盤(pán)后,該 Page Cache Page 就會(huì)變成 Clean Page 并且繼續(xù)存在系統(tǒng)中。
Page Cache 的作用
下面我們通過(guò)一個(gè)例子,來(lái)實(shí)際驗(yàn)證下 Page Cahe 在文件寫(xiě)入以及讀取中的作用。
首先我們用下面的命令來(lái)生成一個(gè) 100M 大小的文件,并分別觀察前后的 buffer/cache 變化,以及 dirty page 的變化情況。
$free -m
total used free shared buff/cache available
Mem: 8192 3415 4450 34 325 4603
Swap: 0 0 0
$dd if=/dev/zero of=testfile.txt bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 0.12828 s, 817 MB/s
$cat /proc/meminfo | grep Dirty
Dirty: 102400 kB
$free -m
total used free shared buff/cache available
Mem: 8192 3416 4348 34 426 4556
Swap: 0 0 0
可以看到 buffer/cache 如期望的增長(zhǎng)了 100M,同時(shí)看 dirty page 的大小也是 100M。在 dirty page 回寫(xiě)之后(變成0),buffer/cache 還是保持著大小不變,說(shuō)明 Page Cache 不會(huì)隨著 ditry page 的回寫(xiě)而消亡。
$time cat testfile.txt &> /dev/null
real 0m0.030s
user 0m0.000s
sys 0m0.030s
然后我們使用讀取命令來(lái)讀取下剛寫(xiě)的文件,由于寫(xiě)入數(shù)據(jù)時(shí)候的 Page Cache 還在,所以現(xiàn)在的讀取其實(shí)是從 Page Cache 中直接讀取的。要看到讀取原始文件的效果,需要我們先利用下面的命令,來(lái)清空下 Page Cache。
$sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
$free -m
total used free shared buff/cache available
Mem: 8192 3419 4476 34 295 4620
Swap: 0 0 0
可以看到 Cache 已經(jīng)被清理了,讓我們?cè)賮?lái)執(zhí)行下面的讀取命令。
$time cat testfile.txt &> /dev/null
real 0m0.174s
user 0m0.000s
sys 0m0.050s
$free -m
total used free shared buff/cache available
Mem: 8192 3420 4353 34 418 4558
Swap: 0 0 0
$time cat testfile.txt &> /dev/null
real 0m0.038s
user 0m0.001s
sys 0m0.027s
可以看到在清理 Page Cache 之后,讀取時(shí)長(zhǎng)有明顯的增加。而再次讀取的時(shí)候,由于又有了 Page Cache 的加持,時(shí)長(zhǎng)跟剛寫(xiě)完時(shí)差不多。
Page Cache 回收
Page Cache 畢竟是為了提高性能占用的物理內(nèi)存,隨著越來(lái)越多的磁盤(pán)數(shù)據(jù)被緩存到內(nèi)存中,Page Cache 變得越來(lái)越大,如果一些重要的任務(wù)需要被 Page Cache 占用的內(nèi)存,內(nèi)核將回收 Page Cache 以支持這些需求。
回收的方式主要是兩種:直接回收和后臺(tái)回收,具體的回收行為,可以使用以下命令查看:
$sar -B 1
Linux 5.10.112-005.ali5000.al8.x86_64 (collection-bullet033061130171.pre.na610) 06/25/2024 _x86_64_ (104 CPU)
05:51:47 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
05:51:48 PM 1352.00 43852.00 319867.00 3.00 345241.00 0.00 0.00 14.00 0.00
05:51:49 PM 8924.00 28396.00 549915.00 26.00 291152.00 0.00 0.00 0.00 0.00
05:51:50 PM 560.00 35836.00 677473.00 0.00 181327.00 0.00 0.00 0.00 0.00
05:51:51 PM 1024.00 25560.00 468166.00 0.00 285465.00 0.00 0.00 0.00 0.00
05:51:52 PM 1024.00 18540.00 451655.00 0.00 216495.00 0.00 0.00 0.00 0.00
05:51:53 PM 1064.00 14836.00 455541.00 2.00 282231.00 0.00 0.00 2126.00 0.00
05:51:54 PM 896.00 18880.00 334210.00 0.00 162607.00 0.00 0.00 0.00 0.00
05:51:55 PM 1024.00 16580.00 492492.00 0.00 162693.00 0.00 0.00 0.00 0.00
05:51:56 PM 1024.00 21184.00 562577.00 0.00 275626.00 0.00 0.00 0.00 0.00
05:51:56 PM 775.76 29745.45 307830.30 0.00 190100.00 0.00 0.00 0.00 0.00
Average: 1837.94 25024.65 473041.80 3.32 242826.37 0.00 0.00 229.37 0.00
- pgscank/s : kswapd(后臺(tái)回收線程) 每秒掃描的 Page 個(gè)數(shù)。
- pgscand/s: Application 在內(nèi)存申請(qǐng)過(guò)程中每秒直接掃描的 Page 個(gè)數(shù)。
- pgsteal/s: 掃描的 Page 中每秒被回收的個(gè)數(shù)。
- %vmeff: pgsteal/(pgscank+pgscand), 回收效率,越接近 100 說(shuō)明系統(tǒng)越安全,越接近 0 說(shuō)明系統(tǒng)內(nèi)存壓力越大。
觸發(fā)條件
1.空間層面
當(dāng)系統(tǒng)中 dirty 的內(nèi)存大于某個(gè)閾值時(shí)。該閾值用在 dirtyable memory 中的占比 dirty_background_ratio(默認(rèn)為10%),或者絕對(duì)的字節(jié)數(shù) dirty_background_bytes(2.6.29內(nèi)核引入)來(lái)指定。如果兩者同時(shí)設(shè)置的話,那么以 bytes 為更高優(yōu)先級(jí)。
此外,還有 dirty_ratio(默認(rèn)為20%)和 dirty_bytes,它們的意思是當(dāng) dirty 的內(nèi)存達(dá)到這個(gè)數(shù)量(屋里太臟),進(jìn)程自己都看不過(guò)去了,寧愿停下手頭的 write 操作(被阻塞,同步),先去把這些 dirty 的 writeback 了(把屋里打掃干凈)。
而如果 dirty 的程度介于 dirty_ratio 和 dirty_background_ratio 之間(10% - 20%),就交給后面要介紹的專(zhuān)門(mén)負(fù)責(zé) writeback 的 background 線程去做就好了(專(zhuān)職的清潔工,異步)。
2.時(shí)間層面
通過(guò)周期性的掃描來(lái)發(fā)現(xiàn)需要回收的 dirty page,掃描間隔用參數(shù):dirty_writeback_interval 控制。發(fā)現(xiàn)存在最近一次更新時(shí)間超過(guò)某個(gè)閾值(參數(shù):dirty_expire_interval)的 Page。如果每個(gè) Page 都維護(hù)最近更新時(shí)間,開(kāi)銷(xiāo)會(huì)很大且掃描會(huì)很耗時(shí),因此具體實(shí)現(xiàn)不會(huì)以 Page 為粒度,而是按 inode 中記錄的 dirtying-time 來(lái)計(jì)算。
dirty_writeback_interval 實(shí)際的參數(shù)為:dirty_writeback_centisecs(默認(rèn)值為 500,單位為 1/100 秒,也就是 5 秒)
dirty_expire_interval 實(shí)際的參數(shù)為:dirty_expire_centisecs(默認(rèn)值為 3000,單位為 1/100 秒,也就是 30 秒)
3.用戶(hù)主動(dòng)發(fā)起。
調(diào)用 sync() / msync() / fsync()。
執(zhí)行線程
在 2.4 內(nèi)核,用一個(gè)叫 bdflush 的線程專(zhuān)門(mén)負(fù)責(zé) writeback 操作。因?yàn)榇疟P(pán) I/O 操作很慢,而操作系統(tǒng)有多個(gè)塊設(shè)備,如果 bdflush 在其中一個(gè)塊設(shè)備上等待 I/O 操作的完成,可能會(huì)需要很長(zhǎng)的時(shí)間,此時(shí)單線程模式的 bdflush 就會(huì)成為影響性能的瓶頸。而且 bdflush 沒(méi)有周期掃描功能。
在2.6 內(nèi)核中,bdflush 和 kupdated 一起被 pdflush(page dirty flush)取代了。pdflush 是一組線程,根據(jù)塊設(shè)備的 I/O 負(fù)載情況,數(shù)量從最少 2 個(gè)到最多 8 個(gè)不等。如果 1 秒內(nèi)沒(méi)有空閑的 pdflush 線程,則會(huì)創(chuàng)建一個(gè);如果 pdflush 線程的空閑時(shí)間超過(guò) 1 秒,則會(huì)被銷(xiāo)毀。一個(gè)塊設(shè)備可能有多個(gè)可以傳輸數(shù)據(jù)的隊(duì)列,為了避免在隊(duì)列上的擁塞,pdflush 線程會(huì)動(dòng)態(tài)的選擇系統(tǒng)中相對(duì)空閑的隊(duì)列。
這種方法在理論上是很優(yōu)秀的,然而現(xiàn)實(shí)的情況是外部 I/O 和 CPU 的速度差異巨大,但 I/O 系統(tǒng)的其他部分并沒(méi)有都使用擁塞控制,因此 pdflush 單獨(dú)使用復(fù)雜的擁塞算法的效果并不明顯,可以說(shuō)是“獨(dú)木難支”。
于是在2.6.32 內(nèi)核中,干脆化繁為簡(jiǎn),直接一個(gè)塊設(shè)備對(duì)應(yīng)一個(gè) thread,這種內(nèi)核線程被稱(chēng)為 flusher threads ,線程名為“writeback”,執(zhí)行體為“wb_workfn”,通過(guò) workqueue 機(jī)制實(shí)現(xiàn)調(diào)度。
無(wú)論是內(nèi)核周期性掃描,還是用戶(hù)手動(dòng)觸發(fā),flusher threads 的 writeback 都是間隔一段時(shí)間才進(jìn)行的,如果在這段時(shí)間內(nèi)系統(tǒng)掉電了(power failure),那還沒(méi)來(lái)得及 writeback 的數(shù)據(jù)修改就面臨丟失的風(fēng)險(xiǎn),這是 Page Cache 機(jī)制存在的一個(gè)缺點(diǎn)。writeback 越頻繁,數(shù)據(jù)因意外丟失的風(fēng)險(xiǎn)越低,但同時(shí) I/O 壓力也越大。技術(shù)本來(lái)就是這樣,沒(méi)有完美的方案,只有最好的方案。
清理 Page Cache
- 運(yùn)行 sync() 將 dirty 的內(nèi)容寫(xiě)回硬盤(pán)
- 通過(guò)修改 proc 系統(tǒng)的 drop_caches 清理 free 的 cache

可以通過(guò) /proc/vmstat 文件判斷是否執(zhí)行過(guò) drop_caches:
$cat /proc/vmstat | grep drop
drop_pagecache 2
drop_slab 2
Page Cache 重要配置參數(shù)
在/proc/sys/vm中有以下文件與回寫(xiě) ditry page 密切相關(guān):
| 配置文件 | 功能 | 默認(rèn)值 |
|---|---|---|
| dirty_background_ratio | 觸發(fā)回刷的臟數(shù)據(jù)占可用內(nèi)存的百分比 | 0 |
| dirty_background_bytes | 觸發(fā)回刷的臟數(shù)據(jù)量 | 10 |
| dirty_bytes | 觸發(fā)同步寫(xiě)的臟數(shù)據(jù)量 | 0 |
| dirty_ratio | 觸發(fā)同步寫(xiě)的臟數(shù)據(jù)占可用內(nèi)存的百分比 | 20 |
| dirty_expire_centisecs | 臟數(shù)據(jù)超時(shí)回刷時(shí)間(單位:1/100s) | 3000 |
| dirty_writeback_centisecs | 回刷進(jìn)程定時(shí)喚醒時(shí)間(單位:1/100s) | 500 |
/proc/sys/vm/dirty_ratio(同步刷盤(pán))
這個(gè)參數(shù)控制文件系統(tǒng)的文件系統(tǒng)寫(xiě)緩沖區(qū)的大小,單位是百分比,表示系統(tǒng)內(nèi)存的百分比,
表示當(dāng)寫(xiě)緩沖使用到系統(tǒng)內(nèi)存多少的時(shí)候,開(kāi)始向磁盤(pán)寫(xiě)出數(shù)據(jù)。
增大之會(huì)使用更多系統(tǒng)內(nèi)存用于磁盤(pán)寫(xiě)緩沖,也可以極大提高系統(tǒng)的寫(xiě)性能。
但是,當(dāng)你需要持續(xù)、恒定的寫(xiě)入場(chǎng)合時(shí),應(yīng)該降低其數(shù)值,一般啟動(dòng)上缺省是 20。
/proc/sys/vm/dirty_background_ratio(異步刷盤(pán))
這個(gè)參數(shù)控制文件系統(tǒng)的 pdflush 進(jìn)程,在何時(shí)刷新磁盤(pán)。
單位是百分比,表示系統(tǒng)內(nèi)存的百分比,意思是當(dāng)寫(xiě)緩沖使用到系統(tǒng)內(nèi)存多少的時(shí)候,pdflush 開(kāi)始向磁盤(pán)寫(xiě)出數(shù)據(jù)。
增大之會(huì)使用更多系統(tǒng)內(nèi)存用于磁盤(pán)寫(xiě)緩沖,也可以極大提高系統(tǒng)的寫(xiě)性能。
但是,當(dāng)你需要持續(xù)、恒定的寫(xiě)入場(chǎng)合時(shí), 應(yīng)該降低其數(shù)值,一般啟動(dòng)上缺省是 10
/proc/sys/vm/dirty_writeback_centisecs
這個(gè)參數(shù)控制內(nèi)核的臟數(shù)據(jù)刷新進(jìn)程 pdflush 的運(yùn)行間隔。
單位是 1/100 秒。缺省數(shù)值是500,也就是 5 秒。
如果你的系統(tǒng)是持續(xù)地寫(xiě)入動(dòng)作,那么實(shí)際上還是降低這個(gè)數(shù)值比較好,這樣可以把尖峰的寫(xiě)操作削平成多次寫(xiě)操。
/proc/sys/vm/dirty_expire_centisecs
這個(gè)參數(shù)聲明 Linux 內(nèi)核寫(xiě)緩沖區(qū)里面的數(shù)據(jù)多“舊”了之后,pdflush 進(jìn)程就開(kāi)始考慮寫(xiě)到磁盤(pán)中去。
單位是 1/100秒。缺省是 3000,也就是 30 秒的數(shù)據(jù)就算舊了,將會(huì)刷新磁盤(pán)。
對(duì)于特別重載的寫(xiě)操作來(lái)說(shuō),這個(gè)值適當(dāng)縮小也是好的,但也不能縮小太多,因?yàn)榭s小太多也會(huì)導(dǎo)致 IO 提高太快。