3. 内存回收机制:PFRA、LRU链表与kswapd内核线程

当系统内存压力增大时,内核必须主动回收部分页面以释放空间。这一过程由 PFRA(Page Frame Reclaiming Algorithm,页框回收算法) 主导,依托 LRU(Least Recently Used,最近最少使用)链表 对页面进行冷热分级,并由内核线程 kswapd 在后台异步触发回收。理解这三者的协作关系,是掌握 OOM Killer 触发前奏的关键。

核心要点:PFRA 是回收策略的制定者,LRU 链表是页面访问热度的分类器,kswapd 是回收动作的执行者。三者缺一不可,共同构成内核内存回收的“铁三角”。

3.1 PFRA:页框回收算法

PFRA 是内核中负责回收物理页面的核心算法。它并非一个单一函数,而是一套策略集合,决定“回收哪些页面”以及“如何回收”。PFRA 主要处理以下四类页面:

页面类型 回收方式 代价 典型场景
干净页(Clean Page) 直接释放 低 文件缓存中未被修改的页面
脏页(Dirty Page) 先回写磁盘,再释放 中 被修改过的文件缓存页
匿名页(Anonymous Page) 交换到 swap 分区/文件 高 进程堆栈、mmap 匿名映射
共享页(Shared Page) 解除映射后按引用计数处理 中 共享库、共享内存

PFRA 的回收优先级遵循“先易后难”原则:优先回收代价最低的干净页,只有在干净页不足时,才会触发脏页回写或匿名页交换。

性能提示:如果系统 swap 配置过小或未配置,PFRA 在回收匿名页时将陷入困境,这会显著增加 OOM Killer 被触发的概率。建议在内存密集型服务器上合理配置 swap 空间。
PFRA 页面回收决策流程 PFRA 回收请求 判断页面类型 干净页 → 直接释放 脏页 → 回写后释放 匿名页 → 交换到 swap 回收完成,增加空闲页计数 回收优先级:干净页 > 脏页 > 匿名页(代价递增)

3.2 LRU 链表:页面冷热分级

PFRA 在决定回收哪些页面时,不能盲目选择。内核使用 LRU(最近最少使用)链表 来追踪每个内存区域的页面访问热度。LRU 链表将页面分为两个主要集合:

  • 活跃链表(active list):存放近期被访问过的“热”页面,回收优先级最低。
  • 非活跃链表(inactive list):存放近期未被访问的“冷”页面,回收优先级最高。

内核通过 页面访问位(PG_referenced) 和 PTE 访问位 来判断页面是否被访问。当页面在非活跃链表中被再次访问时,会被提升回活跃链表;反之,活跃链表中的页面若长时间未被访问,会被降级到非活跃链表。

常见误区:LRU 链表并非严格按“最近最少使用”排序,而是采用近似算法(Clock 算法或双链表策略)来平衡性能与精度。在极端内存压力下,LRU 的近似性可能导致某些“冷但重要”的页面被误回收。
链表类型 页面状态 回收优先级 典型页面
活跃 LRU 链表 近期被访问(热) 低 正在运行的进程堆栈、频繁读写的文件缓存
非活跃 LRU 链表 近期未被访问(冷) 高 已关闭文件的缓存、长时间未操作的匿名页
LRU 链表页面升降级机制 非活跃 LRU 链表 冷页面(回收优先) PG_referenced = 0 活跃 LRU 链表 热页面(保护状态) PG_referenced = 1 再次访问 长时间未访问 内核通过 shrink_list() 函数优先扫描非活跃链表,回收冷页面 PFRA 优先从非活跃链表回收页面

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
kswapd 唤醒与睡眠状态机 kswapd 睡眠 kswapd 唤醒 直接回收(Direct Reclaim) 空闲 < pages_low 空闲 < pages_min 空闲 > pages_high 水位线关系:pages_min < pages_low < pages_high

3.4 三者的协作关系

PFRA、LRU 链表和 kswapd 并非独立工作,而是形成一条完整的回收流水线:

  1. kswapd 被唤醒:当空闲内存低于 pages_low,kswapd 开始执行回收任务。
  2. kswapd 调用 PFRA:PFRA 根据当前内存压力等级,决定回收哪些类型的页面。
  3. PFRA 扫描 LRU 链表:PFRA 优先扫描非活跃 LRU 链表,回收冷页面;如果回收量不足,再扫描活跃链表。
  4. 页面回收与升降级:被回收的页面从 LRU 链表中移除;未被回收但被访问的页面可能在链表中升降级。
  5. kswapd 睡眠:当空闲内存回升至 pages_high,kswapd 停止回收,进入睡眠状态。
故障排查提示:如果系统频繁触发 OOM Killer,首先检查 /proc/vmstat 中的 pgscan_direct 是否持续增长。如果该值很高,说明 kswapd 回收能力不足,系统被迫进入直接回收,此时应检查是否配置了足够的 swap 空间,或者是否存在内存泄漏。