4. 页表回收机制概述
页表回收(Page Table Reclamation)是操作系统在内存压力下,主动释放页表页框(Page Table Page, PTP)以回收物理内存的机制。页表本身占用物理内存,当系统内存紧张时,回收不活跃或非关键的页表页,可以缓解内存压力,同时与 Swap 子系统协同,将对应的用户页换出到磁盘,形成完整的回收闭环。
4.1 页表回收的定义与目标
页表回收是指内核在内存短缺时,遍历并释放那些映射关系已失效、访问频率极低或进程已退出但页表尚未完全释放的页表页框。其核心目标包括:
- 释放物理内存:直接回收页表页框,增加可用页面数量。
- 降低内存碎片:页表页通常为单页(4KB),回收后有助于伙伴系统合并高阶连续内存。
- 协同 Swap 换出:在回收页表前,先换出对应的匿名页,使页表项变为无效,再安全释放页表页。
- 减少 TLB 抖动:回收后进程重新访问时触发缺页,重新建立映射,但可避免长期占用大量页表内存。
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 协同的核心流程,从触发条件到最终释放页表页:
4.5 页表回收的挑战与权衡
下表总结了页表回收的主要挑战及对应设计权衡:
| 挑战 | 风险 | 设计权衡 |
|---|---|---|
| 回收粒度 | 单页表页回收可能导致碎片化 | 优先回收整个 PMD 或 PUD 层级的大页表 |
| 缺页风暴 | 回收后大量进程同时缺页 | 限制单次回收数量,采用 rate-limiting |
| TLB 一致性 | 回收后 TLB 中残留旧映射 | 回收前执行 TLB shootdown(IPI 中断) |
| Swap 死锁 | 回收页表时等待 Swap 写入,而 Swap 又需要内存 | 预留 Swap 写入缓冲区,避免递归回收 |
| NUMA 亲和性 | 回收远端节点页表增加跨节点访问 | 优先回收本地节点页表,保留远端映射 |
4.6 小结
页表回收是内存压力场景下释放物理内存的重要手段,其触发条件与内存水位、进程状态和主动扫描机制紧密相关。核心数据结构包括 struct page 的标志位、mm_struct 的页表层级、shrinker 回调接口以及 Swap 条目编码。回收流程需先换出匿名页、置 PTE 无效,再安全释放页表页框。实际设计中,必须权衡回收粒度、缺页代价和 TLB 一致性,避免因过度回收导致性能反噬。