回收匿名 large folio 的第一步
anon large folio
例如 16KB mTHP
folio_alloc_swap()
先要一块 swap 槽
分配失败
没有可用槽位
split 成基础页
再逐页重试
零散小块
也许还放得下
这套逻辑成立的前提:swap 碎片化,或只是部分可用
整块连续槽位
凑不出来
单页槽位
还有落脚处
拆小才有意义
swap 完全不可用的 3 种情况
swap 被禁用
系统没有 swap 设备
全局 swap 耗尽
一个槽也分不出来
memcg 限额已满
memory.swap.max 到顶
split 出的 small folio
同样拿不到 swap 槽
无处可去
只能被激活回退
拆了也白拆
代价却已经付了
large folio 被拆散
mTHP 的 TLB 与可扩展性收益随之丧失
split 统计虚增
误导后续的调试与调优
只是凑不齐一整块
swap 碎片化
拆小有用
单页仍能落脚
swap 根本不存在
禁用 / 耗尽 / 限额满
拆小无用
small folio 同样没槽
失败的原因,决定拆小之后还走不走得通
让 folio_alloc_swap 把失败原因分类返回
回收侧只在拆分有望时才拆
-E2BIG
这块太大,放不下
THP_SWAP 关闭 · 超出 cluster · 全局有 swap 但凑不齐整块
拆小仍可能放下
-ENOSPC
全局没有 swap 空间
拆到多小都无处可放
拆小无用
-ENOMEM
memcg 的 swap charge 失败
层级内已无剩余容量
拆小无用
分类把两件事分开看
全局 swap 可用性
还有没有 swap 空间
memcg 剩余可 charge 容量
这一层还能不能记账
两层都可能有余地,且当前是 large folio → -E2BIG
其余失败一律不拆,走 activation 保留 large folio
swap 槽其实已经分到
卡住的只是 memcg 计账
撤销槽位
再按两层余量归类
page_counter_margin()
自当前 page_counter 向根逐层
当前
max − usage
上级
max − usage
root
max − usage
取每一层差值的最小值
整条层级里最紧的那道限额还剩多少
它回答:拆小之后还过不过得了 memcg 计账
三层余量取最小
vmscan 回收匿名页
folio_alloc_swap() 失败
返回值
= -E2BIG ?
是
split 成基础页
拆小后重试
其余
activation 路径
large folio 原样保留
shmem 路径 · shmem_writeout()
旧:每次失败都 split
-E2BIG → split 回退
其余 → redirty 后重新激活
swap 碎片化 / 部分可用时的 split 回退完整保留,行为不回归
实测条件
1GB 匿名映射 · 16KB mTHP
memory.swap.max = 0
30 次 process_madvise(MADV_PAGEOUT)
两侧均无实际换出
单次调用中位延迟
743.8 ms
→
181.7 ms
−75.6%
每次运行 large folio split 数
65536
→
0
−100%
无 swap 时这些 split 全是白拆
DaCapo h2 · swap 压力下
wall 与 CPU 时间无统计显著变化
作者:整体收益 minor,且与负载相关
机制层面的收益
mTHP 的 TLB 与可扩展性收益保住
split 统计不再被无 swap 场景虚增
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x