3. 缓存与缓冲调优:Page Cache 与脏页参数
文件系统性能好不好,很大程度上看缓存用得好不好。我见过不少团队,CPU 算力很强,存储介质也不差,但读写一上来就卡顿——问题往往出在缓存参数上。今天咱们就聊聊 Linux 的 Page Cache 机制,以及几个关键的脏页控制参数。
3.1 Page Cache 是什么?
说白了,Page Cache 就是内核用内存来缓存磁盘数据的一种机制。你读一个文件,内核先把数据从磁盘读到内存里,下次再读就直接从内存返回,速度能差好几个数量级。
写操作也一样。你调用 write(),数据先写到 Page Cache 里,标记成“脏页”,然后后台线程再慢慢刷到磁盘。这种“写回”策略能大幅提升写入性能。
核心要点:Page Cache 是文件系统性能的基石。调优缓存参数,本质上就是在“性能”和“数据安全”之间找平衡。
我在一个视频监控项目中遇到过,设备连续写入 1080p 码流,Page Cache 迅速膨胀,脏页比例飙到 60% 以上,结果系统响应变得非常迟钝。嗯,这就是典型的缓存失控。
3.2 脏页控制四兄弟
Linux 内核提供了四个关键参数来控制脏页行为。它们都在 /proc/sys/vm/ 目录下,可以直接查看或修改。
| 参数名 | 默认值 | 作用 |
|---|---|---|
dirty_background_ratio |
10 | 脏页占内存百分比达到此值时,后台内核线程开始异步刷盘 |
dirty_ratio |
20 | 脏页占内存百分比达到此值时,写操作会同步阻塞,强制刷盘 |
dirty_expire_centisecs |
3000 | 脏页在内存中最多停留多久(单位:百分之一秒,3000 = 30 秒) |
dirty_writeback_centisecs |
500 | 后台刷盘线程的唤醒间隔(单位:百分之一秒,500 = 5 秒) |
3.3 参数详解与调优思路
3.3.1 dirty_background_ratio
这个参数是“软阈值”。当脏页比例超过它,内核的 flush 线程就开始干活了——把脏页写回磁盘。注意,这时候写操作不会被阻塞,所以对应用来说是透明的。
我建议在嵌入式设备上把这个值设低一些,比如 5%。为什么?因为嵌入式设备的内存通常比较紧张,如果脏页积压太多,一旦触发 dirty_ratio 的硬阈值,整个系统会瞬间卡死。
经验之谈:如果设备有掉电风险,建议把 dirty_background_ratio 降到 3%~5%。这样脏页能更快落盘,数据更安全。
3.3.2 dirty_ratio
这是“硬阈值”。一旦脏页比例超过它,所有写操作都会被阻塞,直到脏页降到阈值以下。你想想看,这时候应用层会是什么感受?——写入突然卡住,就像水管被堵住了一样。
我曾经在一个工业控制设备上调试过,默认的 20% 导致写入峰值时系统响应延迟从 2ms 飙升到 200ms。后来我把 dirty_ratio 降到 10%,配合调整 dirty_background_ratio 到 5%,问题就解决了。
注意:不要把 dirty_ratio 设得太低,否则频繁触发同步刷盘,写入性能会大幅下降。一般建议 dirty_ratio 是 dirty_background_ratio 的 2 倍左右。
3.3.3 dirty_expire_centisecs
这个参数决定了脏页在内存里的“保质期”。超过这个时间还没写回磁盘的脏页,会被强制刷盘。默认 30 秒,对于大多数场景是够用的。
但在某些场景下需要调整。比如,你做一个数据采集设备,要求写入延迟不超过 5 秒,那就可以把 dirty_expire_centisecs 改成 500(5 秒)。
# 查看当前值
cat /proc/sys/vm/dirty_expire_centisecs
# 临时修改(重启后失效)
echo 500 > /proc/sys/vm/dirty_expire_centisecs
# 永久修改(写入 /etc/sysctl.conf)
echo "vm.dirty_expire_centisecs = 500" >> /etc/sysctl.conf
sysctl -p
3.3.4 dirty_writeback_centisecs
这个参数控制内核刷盘线程的唤醒频率。默认 5 秒唤醒一次,检查是否有脏页需要写回。如果设得太小,CPU 会被频繁唤醒,增加功耗;如果设得太大,脏页积压时间会变长。
对于电池供电的嵌入式设备,我习惯把 dirty_writeback_centisecs 设成 1000(10 秒),减少唤醒次数,延长待机时间。对于性能敏感的设备,可以设成 200(2 秒),让脏页更快落盘。
3.4 调优策略总结
不同的应用场景,调优方向完全不同。我整理了一个对照表,方便你快速选择:
| 场景 | dirty_background_ratio | dirty_ratio | dirty_expire_centisecs | dirty_writeback_centisecs |
|---|---|---|---|---|
| 高吞吐写入(视频监控) | 5% | 10% | 1000(10 秒) | 200(2 秒) |
| 低功耗设备(电池供电) | 10% | 20% | 3000(30 秒) | 1000(10 秒) |
| 数据安全优先(金融终端) | 3% | 6% | 500(5 秒) | 200(2 秒) |
| 通用默认 | 10% | 20% | 3000(30 秒) | 500(5 秒) |
3.5 核心流程:脏页的生命周期
为了让你更直观地理解脏页从产生到落盘的完整过程,我画了一张流程图:
3.6 实战:如何监控脏页状态
调优之前,你得先知道当前系统是什么状态。我常用的几个监控命令:
# 查看脏页总量和比例
cat /proc/meminfo | grep -E "Dirty|Writeback"
# 查看内核脏页参数
sysctl -a | grep dirty
# 实时监控脏页变化(每秒刷新)
watch -n 1 "cat /proc/meminfo | grep -E 'Dirty|Writeback'"
输出示例:
Dirty: 24576 kB
Writeback: 1024 kB
如果 Dirty 值持续增长且不下降,说明刷盘速度跟不上写入速度。这时候就需要调低 dirty_background_ratio 或 dirty_expire_centisecs,让脏页更快落盘。
小技巧:在调试阶段,可以用 echo 3 > /proc/sys/vm/drop_caches 手动清理缓存,但生产环境慎用,会严重影响性能。
3.7 避坑指南
最后分享几个我踩过的坑:
- 别在 NAND Flash 上设太小的 dirty_expire_centisecs——频繁刷盘会加速 Flash 磨损,缩短寿命。我曾经在一个 IoT 设备上设了 2 秒,结果半年后 Flash 就挂了。
- 注意内存大小的影响——同样的比例,512MB 内存和 2GB 内存对应的脏页绝对值差很多。小内存设备要更激进地刷盘。
- 别忽略文件系统本身——ext4 和 ubifs 的刷盘行为不同,调优时要结合文件系统特性一起考虑。
嗯,缓存调优就是这样——没有银弹,只有理解原理后针对场景做取舍。希望这一章能帮你理清思路,少走弯路。