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 监控。
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 标志强制同步。
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 可提升写入吞吐量,但会延长脏页驻留时间,增加数据丢失风险。对于数据库等需要强一致性的应用,建议降低这两个阈值。
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 页数量。
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 压力控制的前提。