3. Page Cache 原理:读缓存、写缓存与脏页回写

Page Cache 是 Linux 内核中用于缓存文件数据在内存中的核心机制。它位于虚拟文件系统(VFS)与块设备之间,所有对常规文件的读写操作(除直接 I/O 外)都会经过 Page Cache。理解 Page Cache 的工作原理,是掌握内核内存与 I/O 性能调优的基础。

3.1 什么是 Page Cache

Page Cache 以内存页(通常为 4KB)为单位,缓存磁盘上的文件数据块。当进程读取文件时,内核首先检查所需数据是否已在 Page Cache 中命中;若命中则直接返回,避免磁盘 I/O;若未命中则触发缺页中断,从磁盘读取数据并填充到缓存中。

核心作用:利用内存的高速访问特性,大幅降低磁盘 I/O 延迟,提升文件系统吞吐量。
关键指标:Page Cache 大小可通过 /proc/meminfo 中的 Cached 字段查看;脏页比例通过 /proc/vmstat 中的 nr_dirty 监控。
Page Cache 在文件 I/O 路径中的位置 用户进程 (User Process) 系统调用 (read/write) VFS (虚拟文件系统) Page Cache (页缓存) 命中则直接返回,未命中则触发磁盘 I/O 块设备层 (Block Layer) 缓存命中:零磁盘 I/O 缓存未命中:触发缺页中断 从磁盘读取并填充缓存

3.2 读缓存与写缓存

Page Cache 对读操作和写操作的处理策略不同,理解这种差异是性能调优的关键。

3.2.1 读缓存 (Read Cache)

  • 预读 (read-ahead):内核检测到顺序读模式时,会提前将后续数据页读入 Page Cache,减少缺页次数。
  • 缓存复用:多个进程读取同一文件时,共享同一份 Page Cache 页,节省内存。
  • LRU 回收:当内存压力增大时,内核通过双链 LRU(活跃/非活跃链表)回收最近最少使用的缓存页。

3.2.2 写缓存 (Write Cache)

  • 延迟写入 (delayed write):进程调用 write() 时,数据先写入 Page Cache,标记为脏页,然后立即返回,不等待磁盘 I/O 完成。
  • 回写 (writeback):脏页在后台由内核线程(如 flush、kworker)异步写回磁盘。
  • 写时复制 (COW):当多个进程共享同一 Page Cache 页且其中一个进程要写入时,内核会复制该页,确保隔离性。
注意:写缓存虽然提升了写入性能,但引入了数据丢失风险。若系统在脏页写回前崩溃,未持久化的数据将丢失。关键业务应使用 fsync() 或 O_SYNC 标志强制同步。
读缓存 vs 写缓存流程 读缓存流程 进程调用 read() 检查 Page Cache 是否命中 命中:直接返回 未命中:磁盘 I/O 填充 Page Cache,返回数据 写缓存流程 进程调用 write() 数据写入 Page Cache 标记为脏页 (Dirty) 立即返回,后台异步回写

3.3 脏页与回写机制

脏页是指 Page Cache 中已被修改但尚未写回磁盘的内存页。内核通过一套复杂的回写机制,在性能与数据安全之间取得平衡。

3.3.1 脏页产生的条件

  • 进程对 mmap 映射的文件页执行写操作。
  • 进程通过 write() 系统调用写入文件,且未使用 O_SYNC 或 O_DIRECT 标志。
  • 文件系统元数据(如 inode、dentry)的修改也会产生脏页。

3.3.2 回写触发条件

触发条件 描述 相关内核参数
脏页比例超限 当脏页占系统内存的比例超过 dirty_background_ratio 时,内核唤醒后台线程开始回写 vm.dirty_background_ratio
脏页绝对量超限 当脏页字节数超过 dirty_background_bytes 时触发回写 vm.dirty_background_bytes
脏页过期时间 脏页在内存中驻留超过 dirty_expire_centisecs 时间(单位:厘秒)后,必须写回 vm.dirty_expire_centisecs
同步写阻塞 当脏页比例达到 dirty_ratio 时,所有写操作将被阻塞,直到脏页比例下降 vm.dirty_ratio
显式同步 进程调用 sync()、fsync()、fdatasync() 强制写回 —
调优要点:增大 dirty_background_ratio 和 dirty_ratio 可提升写入吞吐量,但会延长脏页驻留时间,增加数据丢失风险。对于数据库等需要强一致性的应用,建议降低这两个阈值。
脏页回写触发机制 脏页池 (Dirty Page Pool) 已修改但未写回磁盘的内存页 比例超限 dirty_background_ratio 绝对量超限 dirty_background_bytes 过期时间 dirty_expire_centisecs 同步写阻塞 dirty_ratio 达到 后台回写线程 (flush/kworker) 异步写回磁盘,释放脏页 磁盘 (Disk / SSD)

3.4 Page Cache 的生命周期

Page Cache 中的每一个缓存页都经历从创建、使用、老化到回收的完整生命周期。理解这一周期有助于诊断内存压力和 I/O 性能问题。

3.4.1 生命周期阶段

阶段 状态 说明
1. 分配 (Allocation) 新页 (New) 当缺页中断发生时,内核从伙伴系统分配一个空闲页框,填充文件数据后加入 Page Cache
2. 活跃使用 (Active) 活跃页 (Active) 页被进程频繁访问,位于 LRU 活跃链表,不会被优先回收
3. 非活跃 (Inactive) 非活跃页 (Inactive) 页在一段时间内未被访问,被移入 LRU 非活跃链表,成为回收候选
4. 脏页 (Dirty) 脏页 (Dirty) 页被修改后标记为脏,在写回前不能释放;写回后转为干净页
5. 回收 (Reclaim) 自由页 (Free) 当内存压力增大时,kswapd 或直接回收机制将干净的非活跃页释放回伙伴系统

3.4.2 回收策略

  • 双链 LRU:内核维护 active_list 和 inactive_list 两个链表。页在活跃链表中被访问时提升热度;在非活跃链表中被访问时重新激活。
  • 页面换出 (swap):对于匿名页(非文件映射),回收时需写入 swap 分区;对于文件页,直接丢弃干净页,脏页需先写回。
  • 内存压缩 (compaction):当需要连续大块内存时,内核会移动页框以合并空闲区域。
监控命令:使用 sar -B 查看页回收统计;vmstat 1 查看 si/so 换入换出;/proc/meminfo 查看 Active/Inactive 页数量。
Page Cache 生命周期 1. 分配 (Allocation) 缺页中断 → 分配页框 2. 活跃使用 (Active) 频繁访问,LRU 活跃链表 3. 非活跃 (Inactive) 长时间未访问,回收候选 4. 脏页 (Dirty) 已修改,等待写回 5. 回收 (Reclaim) 释放回伙伴系统 重新激活 干净页:直接丢弃 脏页:先写回再丢弃 匿名页:换出到 swap

3.5 关键内核参数速查

以下是与 Page Cache 生命周期和脏页回写直接相关的内核参数,在后续章节中将详细讲解调优方法。

参数名 默认值 作用
vm.dirty_background_ratio 10 脏页占系统内存百分比达到此值时,后台线程开始回写
vm.dirty_ratio 20 脏页占系统内存百分比达到此值时,写操作同步阻塞
vm.dirty_expire_centisecs 3000 脏页过期时间(厘秒),超过此时间必须写回
vm.dirty_writeback_centisecs 500 回写线程唤醒间隔(厘秒)
vm.vfs_cache_pressure 100 控制 dentry/inode 缓存回收倾向,值越大回收越积极
vm.swappiness 60 控制内核使用 swap 的倾向,值越低越倾向于回收 Page Cache
总结:Page Cache 是 Linux 文件 I/O 性能的基石。读缓存通过预读和复用减少磁盘访问;写缓存通过延迟写入和后台回写提升吞吐量。脏页回写机制在性能与数据安全之间提供可调平衡点。掌握 Page Cache 的生命周期和关键参数,是后续进行 sysctl 调优和 cache 压力控制的前提。