Page Cache

如果说想用一句话说明白什么是 Page Cache,那么我感觉这么说比较合适,“Page Cache 是用于加速对磁盘上数据访问的一部分 RAM”。下面是比较学院派的解释。

Page Cache 是 Linux 内核对磁盘最主要的缓存。大多数情况下,内核在读取或写入磁盘时会引用 Page Cache。新页面被添加到 Page Cache 中,以满足用户空间进程的读取请求。如果该页尚未在缓存中,则会将新条目添加到缓存中并用从磁盘读取的数据填充。该页面将尽量久的保留在缓存中,然后可以由其他进程重用,而无需访问磁盘。
类似的,在向磁盘写入页数据之前,内核会验证相应的页面是否已经包含在 Page Cache 中;如果不在,就会向缓存中添加一个新条目,并用要写入磁盘的数据填充。向磁盘写入的 I/O 数据传输不会立即开始:磁盘更新会被延迟几秒钟,从而给进程进一步修改要写入的数据的机会(这里我感觉是为了批量处理更有效率)。

Page Cache

从图中可以看到 Page Cache是内核管理的内存,也就是说,它属于内核而不属于用户。

在 Linux 中有多种方式可以查询 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 的产生

从图中还可以得到的信息就是 Page Cache 的产生主要有两种方式:

  • Buffered I/O(标准I/O)
  • Memory-Mapped I/O(存储映射I/O)

而直接 I/O (Direct I/O) 不会走 Page Cache 将直接与存储打交道。

I/O

产生方式的区别

  • 标准 I/O 是先写到用户缓冲区(User Space 对应的内存),然后再将用户缓冲区里的数据拷贝(write(2))到内核缓冲区(Page Cache Page对应的内存);如果是读的(read(2))话,则是先从内核缓冲区拷贝到用户缓冲区,再从用户缓冲区读数据,也就是 buffer 和文件内容不存在任何映射关系。

  • 对于存储映射 I/O 而言,则是直接将 Page Cache Page 给映射到用户地址空间,用户直接读写Page Cache Page 中内容。

标准I/O

标准I/O

以标准I/O为例,解释一下 Page Cache 是如何产生的。

  1. 首先往用户缓冲区(buffer)中写入数据,然后调用 write() 方法将 buffer 中的数据拷贝到内核缓冲区(Page Cache Page);
  2. 如果内核缓冲区中没有这个 Page,就会发生 Page Fault(缺页),将先分配一个 Page;
  3. 然后执行数据拷贝,该 Page Cache Page 就变成一个 Dirty Page(脏页);
  4. 最后 Dirty Page 的内容会同步到磁盘,同步到磁盘后,该 Page Cache Page 就会变成 Clean Page 并且继续存在系统中。

Page Cache 的作用

下面我们通过一个例子,来实际验证下 Page Cahe 在文件写入以及读取中的作用。

首先我们用下面的命令来生成一个 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 如期望的增长了 100M,同时看 dirty page 的大小也是 100M。在 dirty page 回写之后(变成0),buffer/cache 还是保持着大小不变,说明 Page Cache 不会随着 ditry page 的回写而消亡。

$time cat testfile.txt &> /dev/null 

real  0m0.030s
user  0m0.000s
sys 0m0.030s

然后我们使用读取命令来读取下刚写的文件,由于写入数据时候的 Page Cache 还在,所以现在的读取其实是从 Page Cache 中直接读取的。要看到读取原始文件的效果,需要我们先利用下面的命令,来清空下 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 已经被清理了,让我们再来执行下面的读取命令。

$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 之后,读取时长有明显的增加。而再次读取的时候,由于又有了 Page Cache 的加持,时长跟刚写完时差不多。

Page Cache 回收

Page Cache 毕竟是为了提高性能占用的物理内存,随着越来越多的磁盘数据被缓存到内存中,Page Cache 变得越来越大,如果一些重要的任务需要被 Page Cache 占用的内存,内核将回收 Page Cache 以支持这些需求。

回收的方式主要是两种:直接回收和后台回收,具体的回收行为,可以使用以下命令查看:

$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(后台回收线程) 每秒扫描的 Page 个数。
  • pgscand/s: Application 在内存申请过程中每秒直接扫描的 Page 个数。
  • pgsteal/s: 扫描的 Page 中每秒被回收的个数。
  • %vmeff: pgsteal/(pgscank+pgscand), 回收效率,越接近 100 说明系统越安全,越接近 0 说明系统内存压力越大。

触发条件

1.空间层面

当系统中 dirty 的内存大于某个阈值时。该阈值用在 dirtyable memory 中的占比 dirty_background_ratio(默认为10%),或者绝对的字节数 dirty_background_bytes(2.6.29内核引入)来指定。如果两者同时设置的话,那么以 bytes 为更高优先级。

此外,还有 dirty_ratio(默认为20%)和 dirty_bytes,它们的意思是当 dirty 的内存达到这个数量(屋里太脏),进程自己都看不过去了,宁愿停下手头的 write 操作(被阻塞,同步),先去把这些 dirty 的 writeback 了(把屋里打扫干净)。

而如果 dirty 的程度介于 dirty_ratio 和 dirty_background_ratio 之间(10% - 20%),就交给后面要介绍的专门负责 writeback 的 background 线程去做就好了(专职的清洁工,异步)。

2.时间层面

通过周期性的扫描来发现需要回收的 dirty page,扫描间隔用参数:dirty_writeback_interval 控制。发现存在最近一次更新时间超过某个阈值(参数:dirty_expire_interval)的 Page。如果每个 Page 都维护最近更新时间,开销会很大且扫描会很耗时,因此具体实现不会以 Page 为粒度,而是按 inode 中记录的 dirtying-time 来计算。

dirty_writeback_interval 实际的参数为:dirty_writeback_centisecs(默认值为 500,单位为 1/100 秒,也就是 5 秒)
dirty_expire_interval 实际的参数为:dirty_expire_centisecs(默认值为 3000,单位为 1/100 秒,也就是 30 秒)

3.用户主动发起。

调用 sync() / msync() / fsync()。

执行线程

在 2.4 内核,用一个叫 bdflush 的线程专门负责 writeback 操作。因为磁盘 I/O 操作很慢,而操作系统有多个块设备,如果 bdflush 在其中一个块设备上等待 I/O 操作的完成,可能会需要很长的时间,此时单线程模式的 bdflush 就会成为影响性能的瓶颈。而且 bdflush 没有周期扫描功能。

在2.6 内核中,bdflush 和 kupdated 一起被 pdflush(page dirty flush)取代了。pdflush 是一组线程,根据块设备的 I/O 负载情况,数量从最少 2 个到最多 8 个不等。如果 1 秒内没有空闲的 pdflush 线程,则会创建一个;如果 pdflush 线程的空闲时间超过 1 秒,则会被销毁。一个块设备可能有多个可以传输数据的队列,为了避免在队列上的拥塞,pdflush 线程会动态的选择系统中相对空闲的队列。

这种方法在理论上是很优秀的,然而现实的情况是外部 I/O 和 CPU 的速度差异巨大,但 I/O 系统的其他部分并没有都使用拥塞控制,因此 pdflush 单独使用复杂的拥塞算法的效果并不明显,可以说是“独木难支”。

于是在2.6.32 内核中,干脆化繁为简,直接一个块设备对应一个 thread,这种内核线程被称为 flusher threads ,线程名为“writeback”,执行体为“wb_workfn”,通过 workqueue 机制实现调度。

无论是内核周期性扫描,还是用户手动触发,flusher threads 的 writeback 都是间隔一段时间才进行的,如果在这段时间内系统掉电了(power failure),那还没来得及 writeback 的数据修改就面临丢失的风险,这是 Page Cache 机制存在的一个缺点。writeback 越频繁,数据因意外丢失的风险越低,但同时 I/O 压力也越大。技术本来就是这样,没有完美的方案,只有最好的方案。

清理 Page Cache

  • 运行 sync() 将 dirty 的内容写回硬盘
  • 通过修改 proc 系统的 drop_caches 清理 free 的 cache
控制项

可以通过 /proc/vmstat 文件判断是否执行过 drop_caches:

$cat /proc/vmstat | grep drop
drop_pagecache 2
drop_slab 2

Page Cache 重要配置参数

在/proc/sys/vm中有以下文件与回写 ditry page 密切相关:

配置文件 功能 默认值
dirty_background_ratio 触发回刷的脏数据占可用内存的百分比 0
dirty_background_bytes 触发回刷的脏数据量 10
dirty_bytes 触发同步写的脏数据量 0
dirty_ratio 触发同步写的脏数据占可用内存的百分比 20
dirty_expire_centisecs 脏数据超时回刷时间(单位:1/100s) 3000
dirty_writeback_centisecs 回刷进程定时唤醒时间(单位:1/100s) 500

/proc/sys/vm/dirty_ratio(同步刷盘)

这个参数控制文件系统的文件系统写缓冲区的大小,单位是百分比,表示系统内存的百分比,
表示当写缓冲使用到系统内存多少的时候,开始向磁盘写出数据。
增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。
但是,当你需要持续、恒定的写入场合时,应该降低其数值,一般启动上缺省是 20。

/proc/sys/vm/dirty_background_ratio(异步刷盘)

这个参数控制文件系统的 pdflush 进程,在何时刷新磁盘。
单位是百分比,表示系统内存的百分比,意思是当写缓冲使用到系统内存多少的时候,pdflush 开始向磁盘写出数据。
增大之会使用更多系统内存用于磁盘写缓冲,也可以极大提高系统的写性能。
但是,当你需要持续、恒定的写入场合时, 应该降低其数值,一般启动上缺省是 10

/proc/sys/vm/dirty_writeback_centisecs

这个参数控制内核的脏数据刷新进程 pdflush 的运行间隔。
单位是 1/100 秒。缺省数值是500,也就是 5 秒。
如果你的系统是持续地写入动作,那么实际上还是降低这个数值比较好,这样可以把尖峰的写操作削平成多次写操。

/proc/sys/vm/dirty_expire_centisecs

这个参数声明 Linux 内核写缓冲区里面的数据多“旧”了之后,pdflush 进程就开始考虑写到磁盘中去。
单位是 1/100秒。缺省是 3000,也就是 30 秒的数据就算旧了,将会刷新磁盘。
对于特别重载的写操作来说,这个值适当缩小也是好的,但也不能缩小太多,因为缩小太多也会导致 IO 提高太快。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容

  • Page Cache是通过将磁盘中的数据缓存到内存中,减少磁盘I/O操作,从而提高性能。此外,还要确保Page C...
    Balram阅读 3,976评论 0 3
  • Buffer Cache Buffer cache是指磁盘设备上的raw data(指不以文件的方式组织)以blo...
    tracy_668阅读 8,470评论 1 8
  • Page Cache层 引入Cache层的目的是为了提高Linux操作系统对磁盘访问的性能。Cache层在内存中缓...
    Bogon阅读 256评论 0 0
  • FYI 广义上Cache的同步方式有两种,即Write Through(写穿)和Write back(写回). 从...
    SnailFast阅读 1,043评论 1 1
  • page cache是kernel实现的disk cache, 这是为了减少磁盘I/O。page writebac...
    july2993阅读 2,032评论 0 1