3. Page Cache的分配与回收:内存压力下的回收机制

Page Cache 作为文件系统与块设备之间的高速缓存层,其分配与回收策略直接影响系统性能与稳定性。当内存压力上升时,内核必须高效地回收 Page Cache 页面,同时确保脏页不会无限增长导致数据丢失风险。本章深入分析 kswapd 与 direct reclaim 两种回收路径,以及脏页比例控制的核心参数。

3.1 Page Cache 的生命周期

Page Cache 页面从分配到回收经历以下关键阶段:

  1. 分配:读文件时触发 page fault,从伙伴系统分配物理页并插入 radix tree(现为 xarray)。
  2. 使用:页面被进程映射、读取或写入,状态从 clean 变为 dirty。
  3. 回写:脏页通过 flusher 线程写回块设备,状态恢复 clean。
  4. 回收:内存不足时,内核将 clean 页从 LRU 链表移除并归还伙伴系统。
关键认知: Page Cache 回收的核心是 LRU(Least Recently Used)链表。内核维护 active 与 inactive 两个 LRU 链表,页面在 inactive 链表尾部被优先回收。
Page Cache 生命周期 分配 (Alloc) 使用 (Clean) 脏页 (Dirty) 回收 (Reclaim) 回写 (Writeback) 回收仅针对 clean 页;脏页必须先回写才能回收

3.2 内存压力下的回收机制

Linux 内核采用两种回收路径应对内存压力:kswapd(后台回收)与 direct reclaim(直接回收)。两者协同工作,确保系统在内存紧张时仍能正常运行。

3.2.1 kswapd:后台异步回收

kswapd 是每个内存节点上的内核线程,在内存水位低于 pages_low 时被唤醒,异步扫描 LRU 链表并回收页面,直到水位恢复到 pages_high 以上。

水位名称 含义 触发动作
pages_min 系统最低可用内存阈值 低于此值触发 direct reclaim
pages_low 低水位警戒线 唤醒 kswapd 开始回收
pages_high 高水位目标线 kswapd 回收至此水位后休眠
性能建议: kswapd 是异步回收,对应用延迟影响较小。建议通过 /proc/sys/vm/min_free_kbytes 适当提高 pages_min,避免频繁触发 direct reclaim。

3.2.2 direct reclaim:同步直接回收

当 kswapd 回收速度跟不上内存分配速度时,分配线程会直接进入回收路径,同步扫描 LRU 并回收页面。direct reclaim 是阻塞式的,会导致明显的分配延迟。

触发条件:

  • 内存分配请求无法从伙伴系统满足
  • kswapd 未能及时将水位恢复到 pages_min 以上
  • 分配标志指定 __GFP_FS 且允许回收
故障排查要点: 如果 /proc/vmstat 中 pgsteal_direct 持续增长,说明系统频繁进入 direct reclaim。此时应检查内存泄漏、Page Cache 过大或脏页比例过高。
内存回收路径决策 内存分配请求 水位 >= pages_low? 直接分配成功 唤醒 kswapd kswapd 回收 水位回升? 是 → kswapd 休眠 direct reclaim direct reclaim 阻塞分配线程

3.3 脏页比例控制

脏页(Dirty Page)是 Page Cache 中已被修改但尚未写回块设备的页面。脏页比例过高会导致:

  • 回收阻塞:脏页必须先回写才能回收,增加内存压力
  • 数据丢失风险:系统崩溃时脏页未写回的数据会丢失
  • I/O 风暴:大量脏页集中回写导致磁盘 I/O 尖峰

内核通过两个核心参数控制脏页比例:

参数 默认值 含义 触发动作
dirty_background_ratio 10% 脏页占可用内存的百分比阈值(后台) 达到此值唤醒 flusher 线程异步回写
dirty_ratio 20% 脏页占可用内存的百分比阈值(强制) 达到此值阻塞写进程,同步回写脏页
参数调整原则:
  • 对于 SSD 等低延迟设备,可适当提高 dirty_ratio(如 30%)以提升写入吞吐
  • 对于 HDD 或延迟敏感应用,应降低 dirty_background_ratio(如 5%)避免 I/O 尖峰
  • 通过 /proc/sys/vm/dirty_writeback_centisecs 控制 flusher 唤醒间隔(默认 500 厘秒)
脏页比例控制与回写触发 0% dirty_background_ratio (10%) dirty_ratio (20%) 强制回写区 (写进程阻塞) 脏页 < 10% 正常写入,无回写触发 10% ≤ 脏页 < 20% 唤醒 flusher 异步回写 脏页 ≥ 20% 写进程阻塞,同步回写 flusher 线程 (pdflush/flush-X) 将脏页批量写回块设备,受 dirty_writeback_centisecs 控制唤醒间隔 块设备 I/O 调度层 调整 dirty_ratio 和 dirty_background_ratio 需重启应用或 sysctl -p 生效

3.4 回收策略与性能调优

在实际故障排查中,以下场景需要重点关注回收策略的调优:

场景 现象 调优方向
高并发写入 写延迟抖动,dirty_ratio 频繁触发 降低 dirty_background_ratio,提高 flusher 线程数
内存不足 OOM dmesg 显示 direct reclaim 次数激增 增大 min_free_kbytes,检查 Page Cache 泄漏
I/O 尖峰 磁盘利用率周期性 100% 降低 dirty_expire_centisecs,分散回写负载
虚拟机/容器 宿主机内存压力导致 guest 卡顿 启用 balloon 驱动,限制 guest Page Cache
监控命令:
# 查看脏页比例
cat /proc/vmstat | egrep "dirty|writeback"
# 查看回收统计
grep -E "pgscan|pgsteal|kswapd" /proc/vmstat
# 实时监控内存回收
watch -n 1 "cat /proc/meminfo | grep -E 'Dirty|Writeback'"

3.5 故障排查案例:脏页堆积导致写延迟

问题描述: 某数据库服务器在业务高峰期出现写延迟飙升,平均写延迟从 2ms 升至 200ms。

排查步骤:

  1. 检查 /proc/meminfo 发现 Dirty 值持续在 8GB 以上,占可用内存 25%
  2. 确认 dirty_ratio 为默认 20%,但系统已进入强制回写区
  3. 查看 /proc/vmstat 发现 pgsteal_direct 计数快速增长
  4. 分析磁盘 I/O 发现 HDD 利用率 100%,回写带宽不足

解决方案:

  • 将 dirty_background_ratio 从 10% 降至 5%,提前触发异步回写
  • 将 dirty_ratio 从 20% 降至 15%,避免脏页堆积过高
  • 增加 flusher 线程数(/proc/sys/vm/nr_pdflush_threads)

调整后,脏页比例稳定在 8% 以下,写延迟恢复至 3ms 以内。

避坑提醒: 不要在生产环境同时大幅调整多个脏页参数。建议每次只修改一个参数,观察 24 小时后再做下一步调整。调整后务必监控 /proc/vmstat 中的回收计数和 I/O 延迟。