4. Page Cache 机制:内核内存与 IO 子系统的核心纽带
Page Cache(页高速缓存)是 Linux 内核中最为关键的缓存机制之一,它位于文件系统与块设备之间,旨在减少磁盘 I/O 访问次数,显著提升文件读写性能。本章将深入剖析 Page Cache 的作用、脏页回写机制以及相关的内核线程(pdflush/flush)。
4.1 Page Cache 的作用与原理
Page Cache 以内存页(通常为 4KB)为单位,缓存磁盘上的文件数据。当进程发起 read() 系统调用时,内核首先检查所需数据是否已在 Page Cache 中命中。若命中(Cache Hit),则直接从内存拷贝数据,避免磁盘 I/O;若未命中(Cache Miss),则触发磁盘读取,并将读取到的数据填充到 Page Cache 中,供后续访问使用。
4.2 脏页(Dirty Page)与回写机制
当进程对 Page Cache 中的页面进行写操作时,数据首先被修改到内存中的缓存页,此时该页被标记为“脏页”(Dirty Page)。脏页表示内存中的数据与磁盘上的数据不一致,必须被写回到磁盘才能保证持久化。这种延迟写入的策略极大地提升了写入性能,但也带来了数据丢失的风险。
4.2.1 脏页回写的触发条件
内核通过多个阈值控制脏页的回写行为,这些阈值可通过 sysctl 参数进行调整:
| sysctl 参数 | 默认值 | 说明 |
|---|---|---|
vm.dirty_background_ratio |
10 | 当脏页占系统内存的百分比达到此值时,内核线程开始后台回写 |
vm.dirty_background_bytes |
0 | 以字节为单位指定后台回写阈值,优先级高于 ratio 参数 |
vm.dirty_ratio |
20 | 当脏页占比达到此值时,进程自身的写操作将被阻塞,直到回写完成 |
vm.dirty_bytes |
0 | 以字节为单位指定阻塞回写阈值 |
vm.dirty_expire_centisecs |
3000 | 脏页在内存中可停留的最大时间(单位:百分之一秒,默认 30 秒) |
vm.dirty_writeback_centisecs |
500 | pdflush 线程定期唤醒的间隔时间(单位:百分之一秒,默认 5 秒) |
dirty_ratio 虽然能提升写入吞吐量,但会显著增加内存压力,并在系统崩溃时导致大量数据丢失。对于数据库等需要强持久性的应用,建议保持默认值或适当降低。
4.2.2 回写流程
脏页回写遵循以下步骤:
- 后台回写触发:当脏页数量超过
dirty_background_ratio时,内核唤醒 pdflush/flush 线程进行异步回写。 - 强制回写触发:当脏页数量超过
dirty_ratio时,进程的写操作被阻塞,同步等待回写完成。 - 过期回写触发:脏页在内存中停留超过
dirty_expire_centisecs时间后,即使未达到阈值,也会被回写。 - 回写执行:内核线程将脏页数据写入块设备,并清除脏页标记。
4.3 pdflush / flush 内核线程
在 Linux 2.6 早期版本中,pdflush 是负责脏页回写的内核线程。从 Linux 2.6.32 开始,内核引入了 flush 线程(也称为 flusher 线程)来替代 pdflush。flush 线程采用每个回写设备(bdi,Backing Device Info)独立线程的设计,相比全局的 pdflush 具有更好的隔离性和可扩展性。
4.3.1 pdflush 与 flush 线程的对比
| 特性 | pdflush(旧) | flush(新) |
|---|---|---|
| 线程模型 | 全局线程池,所有块设备共享 | 每个 bdi 设备独立线程 |
| 并发性 | 线程数固定(2-8 个),可能成为瓶颈 | 按设备动态创建,并发度高 |
| 隔离性 | 一个设备的大量脏页可能阻塞其他设备 | 设备间互不影响 |
| 内核版本 | 2.6.32 之前 | 2.6.32 及之后 |
| 相关 sysctl | vm.dirty_background_ratio 等 |
相同参数,行为一致 |
4.3.2 flush 线程的工作模式
flush 线程(内核中通常命名为 flush-<major>:<minor>)的工作模式如下:
- 周期性唤醒:每隔
dirty_writeback_centisecs(默认 5 秒)唤醒一次,检查是否有过期脏页需要回写。 - 阈值触发:当脏页数量超过
dirty_background_ratio时,立即启动回写。 - 同步阻塞:当脏页数量超过
dirty_ratio时,进程在写操作中同步等待 flush 线程完成回写。 - 内存压力触发:当系统内存不足时,内核直接调用回写机制回收页面。
# 查看所有 flush 内核线程
ps aux | grep flush
# 输出示例(不同设备独立线程):
# root 2 0.0 0.0 0 0 ? S 10:00 0:00 [flush-8:0]
# root 123 0.0 0.0 0 0 ? S 10:00 0:00 [flush-8:16]
# 查看脏页状态
cat /proc/vmstat | grep dirty
# nr_dirty 1234
# nr_writeback 0
4.4 关键 sysctl 参数调优建议
针对不同的应用场景,Page Cache 相关的 sysctl 参数调优策略如下:
| 应用场景 | 推荐调整 | 理由 |
|---|---|---|
| 数据库服务器(如 MySQL、PostgreSQL) | 降低 dirty_background_ratio 至 5,dirty_ratio 至 10 |
减少脏页堆积,降低崩溃时的数据丢失风险,保证写入稳定性 |
| 文件服务器 / 日志服务器 | 适当提高 dirty_background_ratio 至 20,dirty_ratio 至 40 |
利用内存缓存提升写入吞吐量,容忍一定程度的脏页堆积 |
| 虚拟化宿主机 | 降低 dirty_expire_centisecs 至 1000(10 秒) |
加速脏页回写,减少虚拟机内存快照时的数据不一致窗口 |
| SSD 存储环境 | 降低 dirty_writeback_centisecs 至 200(2 秒) |
SSD 随机写入性能好,更频繁的回写不会造成明显性能损失,反而降低数据丢失风险 |
# 设置后台回写阈值为内存的 5%
sysctl -w vm.dirty_background_ratio=5
# 设置强制回写阈值为内存的 10%
sysctl -w vm.dirty_ratio=10
# 设置脏页过期时间为 10 秒
sysctl -w vm.dirty_expire_centisecs=1000
# 查看当前生效值
sysctl vm.dirty_background_ratio vm.dirty_ratio vm.dirty_expire_centisecs
永久生效需写入 /etc/sysctl.conf 或 /etc/sysctl.d/ 目录下的配置文件。
4.5 总结
Page Cache 是 Linux 内核 I/O 子系统的核心组件,通过缓存文件数据显著提升读写性能。脏页回写机制在性能与数据安全之间取得平衡,而 pdflush/flush 内核线程负责执行具体的回写任务。理解这些机制并合理调整相关 sysctl 参数,是系统性能调优的关键环节。