Page Cache 是 Linux 内核强制介入 I/O 路径的内存缓存机制,所有常规 read()/write() 均经过它;它加速重复读、聚合写,但导致 dd 或 cat 测速虚高或二次执行极快;绕过需 O_DIRECT(严格对齐且禁用 mmap),代价是丧失内核优化。

Linux 的文件系统缓存(Page Cache)不是“可选优化”,而是 I/O 路径中默认强制介入的环节——所有常规 read() / write() 系统调用都会经过它,绕不开,也关不掉(除非换接口或改内核)。
Page Cache 是什么,为什么它总在“偷偷”影响你的 dd 或 cat
Page Cache 是内核管理的一块内存区域,用于缓存磁盘文件的页(page),本质是把最近读过或即将写入的数据暂存在 RAM 中。它的存在让重复读变快、顺序写聚合、小写批量刷盘——但也会导致你测出的 dd if=/dev/zero of=test bs=4K count=10000 速度虚高(实际写入还没落盘),或者 cat large.log | grep pattern 第二次快得离谱(全在内存里搜了)。
关键点:
-
open()默认启用缓存;想绕过必须显式加O_DIRECT标志(且要求对齐、不能与 mmap 混用) -
write()返回成功 ≠ 数据已写入磁盘,只表示进了 Page Cache;落盘由pdflush或writeback机制异步触发 -
read()命中缓存时,耗时是纳秒级;未命中则触发真实磁盘 I/O,毫秒级起步
如何判断当前操作是否受 Page Cache 干扰
别猜,看 /proc/meminfo 和 /proc/sys/vm/stat。最直接的方式是对比两次相同操作的耗时,并观察缓存状态变化:
echo 3 > /proc/sys/vm/drop_caches # 清空 pagecache + dentries + inodes(仅用于测试!) time dd if=/dev/sda1 of=/dev/null bs=1M count=1000 cat /proc/meminfo | grep -i "cached\|buffers"
再跑一次 dd,如果第二轮明显更快且 Cached: 值大幅上升,说明 Page Cache 正在起作用。
注意:
-
drop_caches只清空干净页(clean pages),脏页(dirty pages)仍会保留并等待 writeback -
Buffers:是块设备层缓存(如 ext4 日志元数据),和 Page Cache 分属不同层级,但常被一起提及 - 生产环境禁止周期性执行
drop_caches,它会强制驱逐有效缓存,反而恶化性能
O_DIRECT 能否彻底绕过缓存?代价是什么
可以绕过 Page Cache,但代价显著:每次 I/O 都直通块设备,失去内核缓冲、预读、写聚合等优化。它适合数据库等自己做缓存的场景,不适合普通应用。
使用前提严格:
- 用户缓冲区地址和长度必须按硬件扇区对齐(通常是 512B 或 4K)
- 文件 offset 和
read()/write()长度也需对齐 - 不能与
mmap()同一文件混用(可能引发 SIGBUS) - 部分文件系统(如 XFS)对
O_DIRECT支持更稳定;ext4 在某些版本有 bug
示例(Python 中打开文件):
import os
fd = os.open("/tmp/test", os.O_RDWR | os.O_DIRECT)
# 注意:buffer 必须是 memoryview 或 ctypes 数组,且地址对齐影响 I/O 性能的关键 tunables(不是“调优万金油”)
内核提供了几个可调参数,但多数情况下不该动,默认值已适配通用负载。真要调整,务必结合 iostat -x 1 和 vmstat 1 观察效果:
-
/proc/sys/vm/dirty_ratio:内存中脏页占比上限(默认 20%),超限后进程会阻塞写入直到回写启动 -
/proc/sys/vm/dirty_background_ratio:后台回写启动阈值(默认 10%),设太低会导致频繁唤醒writeback线程 -
/proc/sys/vm/vfs_cache_pressure:控制 dentry/inode 缓存回收倾向,值越高越激进(默认 100)
这些参数的作用对象是“缓存策略”,不是“开关”。调错反而会让 Page Cache 更早被踢出,增加磁盘压力。
真正需要关注的,是你的应用是否在误用缓存——比如反复 read() 小文件却不 close,或写完没 fsync() 却以为数据已持久化。











