背景

大页(THP)在内存不足时会被推迟分裂:未充分使用的大页先挂到 deferred split 队列,由 THP shrinker 在回收时择机拆分,把没被映射的部分还给伙伴分配器。folio 迁移(如 NUMA 平衡迁移)走 __folio_migrate_mapping,它会把源 folio 从 deferred split 队列里移除。

┌────────────────────────────┐
│ 迁移把源页移出延迟分裂队列 │
└─────────────┬──────────────┘
              ▼
┌────────────────────────────┐
│     目的页却不重新入队     │  未充分使用的大页静默脱离队列
└─────────────┬──────────────┘
              ▼
┌────────────────────────────┐
│   收缩器无法及时拆分它们   │  用户看到的空闲内存变少
└────────────────────────────┘

但移除之后,目的 folio 从来不会被重新入队。一次 NUMA 迁移下来,原本在延迟分裂队列上的未充分使用大页,就静默地从队列上消失了。

问题

  • 迁移把源 folio 移出延迟分裂队列
  • 目的 folio 从不重新入队
  • 未充分使用的大页静默脱队
  • THP shrinker 无法及时拆分,用户空闲内存变少

方案

核心是迁移前后把队列状态衔接起来:迁移前先记下源 folio 是否在延迟分裂队列、以及它是不是 partially mapped,迁移成功后照着这个状态把目的 folio 重新入队。

旧:源页移出,目的页不归队
┌────────────────────────┐
│     迁移只移出源页     │
└───────────┬────────────┘
            ▼
┌────────────────────────┐
│ 目的页脱离延迟分裂队列 │
└────────────────────────┘

新:按状态把目的页归队
┌──────────────────────────────┐
│   迁移前记下源页是否在队列   │
└──────────────┬───────────────┘
               ▼
┌──────────────────────────────┐
│ 迁移成功后按状态把目的页归队 │  部分映射状态一并传递,两种情况都对
└──────────────────────────────┘

migrate_folio_move 运行时,没有 pin 的 partially mapped folio 已经被 migrate_pages_batch 提前拆掉了,所以此刻仍在延迟分裂队列上的只剩两类:

  • 有 pin 的 partially mapped folio(拆分失败留下)
  • 完全映射但可能未被充分利用的 folio把记下的 partially_mapped 状态传给 deferred_split_folio,目的 folio 在这两种情况下都能被正确重新入队。

收益

作者未提供量化 benchmark。从机制与回归动机推断的预期收益:

  • 未充分使用的大页在 NUMA 迁移后不再静默脱离延迟分裂队列
  • THP shrinker 能重新及时拆分这些大页、归还未映射部分
  • 用户不再因迁移丢失这些本应回收的空闲内存

作者说明回归动机:因为 THP 从 deferred_list 被移除,THP shrinker 无法及时拆分 underutilized THP,结果用户会显示比以前更少的空闲内存。