缓冲与页缓存:write-back、脏页与 O_DIRECT
页缓存(page cache)把文件数据以页为单位缓存在空闲内存中,配合 write-back 策略(写入先进内存、异步刷盘)与预读,用内存的快弥合磁盘的慢——它是 Linux 下文件 I/O 性能的默认引擎。
一句话定义
页缓存(page cache)把文件数据以页为单位缓存在空闲内存中,配合 write-back 策略(写入先进内存、异步刷盘)与预读,用内存的快弥合磁盘的慢——它是 Linux 下文件 I/O 性能的默认引擎。
为什么重要
"第二次读同一个文件快了几十倍""dd 完成但数据其实没落盘""拷贝大文件后系统其他操作变卡"——全部是页缓存在起作用或起反作用。它与 kp-024 的持久化语义、kp-018 的内存回收直接耦合:页缓存既是加速器,也是"最容易被回收的内存",理解它才能正确解读 free 命令与 I/O 基准测试结果。
前置知识
kp-024(write 不落盘的语义);kp-018(页与回收)。
核心概念
- 页缓存:以 4KB 页缓存文件内容与元数据,读命中直接服务,未命中触发磁盘读并填充缓存。
- 写策略:write-back vs write-through:Linux 文件 I/O 默认 write-back(标脏即返回),直写设备可用 O_SYNC。
- 脏页(dirty page):内容已改但未落盘的缓存页,由内核按比例阈值触发后台刷写(writeback 线程)。
- 预读(readahead):检测顺序访问模式,提前批量读入后续页。
- O_DIRECT:绕过页缓存直达设备,数据库自管缓存时的选择。
- 双缓冲:用户缓冲区与内核缓存之间的两次拷贝,是 zero-copy 技术(sendfile)的优化动机。
原理与机制
读路径与写路径:
read(): 应用缓冲 <-- 拷贝 <-- 页缓存命中? 是: 直接返回 (内存速度)
未命中: 触发磁盘读+预读填充 -> 再拷贝
write(): 应用缓冲 -- 拷贝 --> 页缓存(标脏) -> 立即返回
脏页比例超 vm.dirty_ratio -> writeback 线程异步刷盘
fsync(): 指定文件的脏页强制走完 存储路径 (kp-024 的语义)
刷写触发条件(可调):vm.dirty_ratio(脏页占内存比例上限,超限写入进程被迫同步刷)、vm.dirty_background_ratio(后台刷写启动线)、以及 30 秒老化时间。两次读同一文件的实验是页缓存最直观的证明:第一次受磁盘限制,第二次全走内存,time 输出差一个数量级——做 I/O 基准时若不 drop 缓存或用 O_DIRECT,测的其实是内存带宽。
公式或模型
缓存对平均访问时间的作用与 kp-018 的 EAT 同构:EAT = 命中率 × 内存时间 + 未命中率 × 磁盘时间;命中率从 0 到 99%,平均时间从毫秒级降到接近内存级——这就是缓存存在的全部数学理由。
直观类比
页缓存像书店门口的畅销书展台:热门书不用每次都去仓库(磁盘)搬;write-back 像先在展台上改价签、打烊前统一回写库存系统;O_DIRECT 则是批发商要求"直接进仓库、别经过展台"。
实例或案例
free -h # buff/cache 列即页缓存规模
time cat 1g-file > /dev/null # 第一次与第二次对比, 差异即页缓存收益
vmstat 1 # bi/bo 列为块设备读写量, 全部命中缓存时趋近 0
sync; echo 3 > /proc/sys/vm/drop_caches # 清缓存做基准(生产环境慎用)
sysctl vm.dirty_ratio vm.dirty_background_ratio # 脏页阈值
常见误区
- 认为"缓存越大越好":页缓存挤占匿名页会引发回收与 swap(kp-018),写密集场景脏页堆积还会造成 IO 突刺。
- 用 dd 不加参数就报"磁盘写入速度":默认 write-back 下测的是缓存速度,必须 oflag=dsync 或 fdatasync 才是落盘速度。
- 认为 O_DIRECT 是"更快"的选项:它放弃缓存与预读,只在应用自管缓存(数据库)且对齐严格时才有收益。
与其他知识点的关系
脏页刷写与 kp-024 的 fsync 是"异步与同步"两极;回收逻辑由 kp-018 的置换框架统一执行;sendfile 等 zero-copy 优化以双缓冲拷贝为背景。
延伸阅读
《UNIX 操作系统设计》缓冲缓存章(历史原型);内核文档 Documentation/admin-guide/sysctl/vm。
自测题
- 为什么第二次 cat 同一文件快很多?
答:第一次读已把文件页装入页缓存,第二次全部命中内存,不再触发磁盘 I/O。
- write-back 策略的崩溃风险由什么机制兜底?
答:断电丢失脏页是已知代价,可靠性由应用显式 fsync 与文件系统日志(kp-024)承担,缓存只负责性能。
- 数据库为什么常用 O_DIRECT?
答:数据库自带精密的缓冲池,页缓存会形成双重缓存浪费内存且引入不可控刷写时机,绕开它可获得确定性的 I/O 控制。