4. 页表回收机制概述

页表回收(Page Table Reclamation)是操作系统在内存压力下,主动释放页表页框(Page Table Page, PTP)以回收物理内存的机制。页表本身占用物理内存,当系统内存紧张时,回收不活跃或非关键的页表页,可以缓解内存压力,同时与 Swap 子系统协同,将对应的用户页换出到磁盘,形成完整的回收闭环。

4.1 页表回收的定义与目标

页表回收是指内核在内存短缺时,遍历并释放那些映射关系已失效、访问频率极低或进程已退出但页表尚未完全释放的页表页框。其核心目标包括:

  • 释放物理内存:直接回收页表页框,增加可用页面数量。
  • 降低内存碎片:页表页通常为单页(4KB),回收后有助于伙伴系统合并高阶连续内存。
  • 协同 Swap 换出:在回收页表前,先换出对应的匿名页,使页表项变为无效,再安全释放页表页。
  • 减少 TLB 抖动:回收后进程重新访问时触发缺页,重新建立映射,但可避免长期占用大量页表内存。
关键指标:页表回收效率通常用 回收页表页数 / 扫描页表页数 衡量,理想情况下应高于 60%。若回收率过低,说明页表页仍被频繁使用,回收反而会引发大量缺页中断。

4.2 触发条件

页表回收并非持续运行,而是在特定内存压力场景下被激活。主要触发条件如下表所示:

触发条件 描述 典型阈值
直接内存回收(Direct Reclaim) 进程分配内存时,伙伴系统无法满足请求,直接进入回收路径。 空闲页数 < 低水位线(low watermark)
kswapd 内核线程唤醒 周期性检查内存水位,当空闲页数低于 pages_low 时唤醒。 空闲页数 < pages_low
内存压力通知(Memory Pressure Notification) cgroup 或系统级内存压力事件触发。 PSI 指标 > 60%
进程退出或 exec 调用 进程释放地址空间时,批量回收页表页。 进程 mm_struct 引用计数归零
主动式页表压缩(Proactive Compaction) 内核在后台定期扫描并回收长期未使用的页表页。 每 5 秒扫描一次
设计要点:直接回收路径中,页表回收优先级应低于匿名页和文件页回收,因为重建页表映射的代价较高。通常先尝试换出匿名页,再考虑释放页表页。

4.3 核心数据结构

页表回收依赖以下关键数据结构来追踪页表页的状态、引用关系和回收候选:

4.3.1 页表页描述符(struct page)

每个物理页框对应一个 struct page,页表页通过 page->mapping 和 page->index 字段关联到所属的 mm_struct 和虚拟地址范围。回收时通过 PG_table 标志位判断是否为页表页。

// 判断页表页的核心宏
#define PG_table 1  // 在 page->flags 中置位

static inline int PageTable(struct page *page) {
    return test_bit(PG_table, &page->flags);
}

4.3.2 mm_struct 与页表层级

每个进程的 mm_struct 维护页表根指针 pgd,以及各级页表页的链表或基数树。回收时从 pgd 开始向下遍历 P4D、PUD、PMD、PTE 层级,标记可回收页。

struct mm_struct {
    pgd_t *pgd;                // 页全局目录基址
    atomic_t mm_count;         // 引用计数
    struct list_head mmlist;   // 全局 mm 链表
    // ... 其他字段
};

4.3.3 回收候选链表(lru_cache 与 shrinker)

内核通过 shrinker 接口注册页表回收回调,扫描 mm_struct 链表,将符合条件的页表页加入临时回收候选链表。关键字段包括:

  • page->lru:用于将页表页链接到回收候选链表。
  • mm->nr_ptes:统计当前进程使用的页表页数量,辅助判断回收优先级。
  • shrinker->count_objects:返回可回收页表页数量。
  • shrinker->scan_objects:执行实际回收操作。

4.3.4 页表项(PTE)与 Swap 条目

回收页表页前,必须确保对应的 PTE 已置为无效或已替换为 Swap 条目。Swap 条目编码在 PTE 中,通过 pte_to_swp_entry 转换。

// Swap 条目编码在 PTE 中
#define SWP_TYPE_SHIFT 1
#define SWP_OFFSET_SHIFT (SWP_TYPE_SHIFT + MAX_SWAPFILES_SHIFT)

static inline swp_entry_t pte_to_swp_entry(pte_t pte) {
    swp_entry_t entry;
    entry.val = (pte_val(pte) >> SWP_TYPE_SHIFT) & 
                ((1UL << (BITS_PER_LONG - SWP_TYPE_SHIFT)) - 1);
    return entry;
}

4.4 页表回收流程概览

下图展示了页表回收与 Swap 协同的核心流程,从触发条件到最终释放页表页:

页表回收与 Swap 协同流程 页表回收与 Swap 协同流程 触发条件:内存低于低水位 扫描 mm_struct 链表,收集页表页 判断:页表页是否可回收? 否:跳过 是:继续 换出匿名页到 Swap 置 PTE 为无效/Swap 条目 释放页表页框到伙伴系统 更新 mm->nr_ptes 与全局统计 回收完成

4.5 页表回收的挑战与权衡

性能陷阱:页表回收后,进程再次访问对应虚拟地址时,会触发缺页中断(Page Fault),需要重新分配页表页并建立映射。如果回收过于激进,可能导致频繁的缺页和页表重建,反而降低系统吞吐量。因此,回收策略必须结合访问频率(通过 PTE 的 Accessed 位)和进程优先级进行精细控制。

下表总结了页表回收的主要挑战及对应设计权衡:

挑战 风险 设计权衡
回收粒度 单页表页回收可能导致碎片化 优先回收整个 PMD 或 PUD 层级的大页表
缺页风暴 回收后大量进程同时缺页 限制单次回收数量,采用 rate-limiting
TLB 一致性 回收后 TLB 中残留旧映射 回收前执行 TLB shootdown(IPI 中断)
Swap 死锁 回收页表时等待 Swap 写入,而 Swap 又需要内存 预留 Swap 写入缓冲区,避免递归回收
NUMA 亲和性 回收远端节点页表增加跨节点访问 优先回收本地节点页表,保留远端映射

4.6 小结

页表回收是内存压力场景下释放物理内存的重要手段,其触发条件与内存水位、进程状态和主动扫描机制紧密相关。核心数据结构包括 struct page 的标志位、mm_struct 的页表层级、shrinker 回调接口以及 Swap 条目编码。回收流程需先换出匿名页、置 PTE 无效,再安全释放页表页框。实际设计中,必须权衡回收粒度、缺页代价和 TLB 一致性,避免因过度回收导致性能反噬。