Page Cache

如果說(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

從圖中可以看到 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ǔ)打交道。

I/O

產(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

以標(biāo)準(zhǔn)I/O為例,解釋一下 Page Cache 是如何產(chǎn)生的。

  1. 首先往用戶(hù)緩沖區(qū)(buffer)中寫(xiě)入數(shù)據(jù),然后調(diào)用 write() 方法將 buffer 中的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)(Page Cache Page);
  2. 如果內(nèi)核緩沖區(qū)中沒(méi)有這個(gè) Page,就會(huì)發(fā)生 Page Fault(缺頁(yè)),將先分配一個(gè) Page;
  3. 然后執(zhí)行數(shù)據(jù)拷貝,該 Page Cache Page 就變成一個(gè) Dirty Page(臟頁(yè));
  4. 最后 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
控制項(xiàng)

可以通過(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 提高太快。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Page Cache是通過(guò)將磁盤(pán)中的數(shù)據(jù)緩存到內(nèi)存中,減少磁盤(pán)I/O操作,從而提高性能。此外,還要確保Page C...
    Balram閱讀 4,204評(píng)論 0 3
  • Buffer Cache Buffer cache是指磁盤(pán)設(shè)備上的raw data(指不以文件的方式組織)以blo...
    tracy_668閱讀 9,233評(píng)論 1 8
  • Page Cache層 引入Cache層的目的是為了提高Linux操作系統(tǒng)對(duì)磁盤(pán)訪問(wèn)的性能。Cache層在內(nèi)存中緩...
    Bogon閱讀 386評(píng)論 0 0
  • FYI 廣義上Cache的同步方式有兩種,即Write Through(寫(xiě)穿)和Write back(寫(xiě)回). 從...
    SnailFast閱讀 1,192評(píng)論 1 1
  • page cache是kernel實(shí)現(xiàn)的disk cache, 這是為了減少磁盤(pán)I/O。page writebac...
    july2993閱讀 2,154評(píng)論 0 1

友情鏈接更多精彩內(nèi)容