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 核心流程:脏页的生命周期

为了让你更直观地理解脏页从产生到落盘的完整过程,我画了一张流程图:

脏页生命周期与刷盘流程 应用调用 write() 数据写入 Page Cache(标记脏页) 脏页比例是否超过阈值? 后台线程异步刷盘 写操作同步阻塞刷盘 脏页写回磁盘(落盘完成) dirty_background_ratio 触发异步刷盘(左),dirty_ratio 触发同步阻塞(右)

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 的刷盘行为不同,调优时要结合文件系统特性一起考虑。

嗯,缓存调优就是这样——没有银弹,只有理解原理后针对场景做取舍。希望这一章能帮你理清思路,少走弯路。