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 中,供后续访问使用。

核心收益:Page Cache 将慢速的磁盘访问转化为快速的内存访问,尤其对于频繁访问的热点数据,命中率可达 90% 以上,是数据库、Web 服务器等 I/O 密集型应用性能的关键保障。
Page Cache 读写路径示意图 用户进程 系统调用层 VFS Page Cache (内存页缓存) 命中: 直接返回 未命中: 触发磁盘 I/O 文件系统 块设备层 说明:用户进程通过系统调用访问文件,VFS 层首先查询 Page Cache。命中则直接返回数据,未命中则经文件系统向块设备发起 I/O。

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 回写流程

脏页回写遵循以下步骤:

  1. 后台回写触发:当脏页数量超过 dirty_background_ratio 时,内核唤醒 pdflush/flush 线程进行异步回写。
  2. 强制回写触发:当脏页数量超过 dirty_ratio 时,进程的写操作被阻塞,同步等待回写完成。
  3. 过期回写触发:脏页在内存中停留超过 dirty_expire_centisecs 时间后,即使未达到阈值,也会被回写。
  4. 回写执行:内核线程将脏页数据写入块设备,并清除脏页标记。
脏页回写触发流程 进程写操作 Page Cache (脏页) 阈值判断 条件1: 后台阈值 条件2: 强制阈值 条件3: 过期时间 pdflush 回写线程

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 线程:
# 查看所有 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 参数,是系统性能调优的关键环节。