3. 内存回收机制:PFRA、LRU链表与kswapd内核线程
当系统内存压力增大时,内核必须主动回收部分页面以释放空间。这一过程由 PFRA(Page Frame Reclaiming Algorithm,页框回收算法) 主导,依托 LRU(Least Recently Used,最近最少使用)链表 对页面进行冷热分级,并由内核线程 kswapd 在后台异步触发回收。理解这三者的协作关系,是掌握 OOM Killer 触发前奏的关键。
3.1 PFRA:页框回收算法
PFRA 是内核中负责回收物理页面的核心算法。它并非一个单一函数,而是一套策略集合,决定“回收哪些页面”以及“如何回收”。PFRA 主要处理以下四类页面:
| 页面类型 | 回收方式 | 代价 | 典型场景 |
|---|---|---|---|
| 干净页(Clean Page) | 直接释放 | 低 | 文件缓存中未被修改的页面 |
| 脏页(Dirty Page) | 先回写磁盘,再释放 | 中 | 被修改过的文件缓存页 |
| 匿名页(Anonymous Page) | 交换到 swap 分区/文件 | 高 | 进程堆栈、mmap 匿名映射 |
| 共享页(Shared Page) | 解除映射后按引用计数处理 | 中 | 共享库、共享内存 |
PFRA 的回收优先级遵循“先易后难”原则:优先回收代价最低的干净页,只有在干净页不足时,才会触发脏页回写或匿名页交换。
3.2 LRU 链表:页面冷热分级
PFRA 在决定回收哪些页面时,不能盲目选择。内核使用 LRU(最近最少使用)链表 来追踪每个内存区域的页面访问热度。LRU 链表将页面分为两个主要集合:
- 活跃链表(active list):存放近期被访问过的“热”页面,回收优先级最低。
- 非活跃链表(inactive list):存放近期未被访问的“冷”页面,回收优先级最高。
内核通过 页面访问位(PG_referenced) 和 PTE 访问位 来判断页面是否被访问。当页面在非活跃链表中被再次访问时,会被提升回活跃链表;反之,活跃链表中的页面若长时间未被访问,会被降级到非活跃链表。
| 链表类型 | 页面状态 | 回收优先级 | 典型页面 |
|---|---|---|---|
| 活跃 LRU 链表 | 近期被访问(热) | 低 | 正在运行的进程堆栈、频繁读写的文件缓存 |
| 非活跃 LRU 链表 | 近期未被访问(冷) | 高 | 已关闭文件的缓存、长时间未操作的匿名页 |
3.3 kswapd 内核线程:后台回收守护者
kswapd 是内核中负责 异步内存回收 的内核线程。每个内存节点(NUMA 架构下每个 NUMA 节点)都有一个独立的 kswapd 线程。kswapd 的唤醒与睡眠由 内存水位线(watermark) 控制:
- pages_min(最低水位线):内存极度紧缺,必须立即回收。
- pages_low(低水位线):内存压力较大,kswapd 开始后台回收。
- pages_high(高水位线):内存充足,kswapd 睡眠。
当空闲内存低于 pages_low 时,kswapd 被唤醒,开始扫描 LRU 链表并回收页面,直到空闲内存回升至 pages_high 以上才进入睡眠。如果 kswapd 的回收速度跟不上内存分配速度,空闲内存持续下降至 pages_min,内核将触发 直接回收(direct reclaim),此时分配进程自身同步参与回收,系统性能急剧下降。
/proc/zoneinfo 可以查看每个内存区域的水位线设置。如果 pgscan_kswapd 和 pgscan_direct 两个计数器持续增长,说明系统内存压力较大,kswapd 回收能力不足。
# 查看内存水位线(单位:页,通常 4KB/页)
$ cat /proc/zoneinfo | grep -E "min|low|high"
Node 0, zone Normal
min 12345
low 15431
high 18517
# 查看 kswapd 回收统计
$ cat /proc/vmstat | grep pgscan
pgscan_kswapd 89234
pgscan_direct 1567
3.4 三者的协作关系
PFRA、LRU 链表和 kswapd 并非独立工作,而是形成一条完整的回收流水线:
- kswapd 被唤醒:当空闲内存低于
pages_low,kswapd 开始执行回收任务。 - kswapd 调用 PFRA:PFRA 根据当前内存压力等级,决定回收哪些类型的页面。
- PFRA 扫描 LRU 链表:PFRA 优先扫描非活跃 LRU 链表,回收冷页面;如果回收量不足,再扫描活跃链表。
- 页面回收与升降级:被回收的页面从 LRU 链表中移除;未被回收但被访问的页面可能在链表中升降级。
- kswapd 睡眠:当空闲内存回升至
pages_high,kswapd 停止回收,进入睡眠状态。
/proc/vmstat 中的 pgscan_direct 是否持续增长。如果该值很高,说明 kswapd 回收能力不足,系统被迫进入直接回收,此时应检查是否配置了足够的 swap 空间,或者是否存在内存泄漏。