3. Page Cache的分配与回收:内存压力下的回收机制
Page Cache 作为文件系统与块设备之间的高速缓存层,其分配与回收策略直接影响系统性能与稳定性。当内存压力上升时,内核必须高效地回收 Page Cache 页面,同时确保脏页不会无限增长导致数据丢失风险。本章深入分析 kswapd 与 direct reclaim 两种回收路径,以及脏页比例控制的核心参数。
3.1 Page Cache 的生命周期
Page Cache 页面从分配到回收经历以下关键阶段:
- 分配:读文件时触发 page fault,从伙伴系统分配物理页并插入 radix tree(现为 xarray)。
- 使用:页面被进程映射、读取或写入,状态从 clean 变为 dirty。
- 回写:脏页通过 flusher 线程写回块设备,状态恢复 clean。
- 回收:内存不足时,内核将 clean 页从 LRU 链表移除并归还伙伴系统。
关键认知: Page Cache 回收的核心是 LRU(Least Recently Used)链表。内核维护 active 与 inactive 两个 LRU 链表,页面在 inactive 链表尾部被优先回收。
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 过大或脏页比例过高。
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 厘秒)
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。
排查步骤:
- 检查
/proc/meminfo发现 Dirty 值持续在 8GB 以上,占可用内存 25% - 确认 dirty_ratio 为默认 20%,但系统已进入强制回写区
- 查看
/proc/vmstat发现 pgsteal_direct 计数快速增长 - 分析磁盘 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 延迟。