背景
defrag_mode 是 page_alloc 的碎片整理模式:启用后强制 ALLOC_NOFRAGMENT,优先靠 reclaim/compaction 释放出"正确迁移类型"的页,只在最后才允许跨迁移类型 fallback,借此保持 pageblocks 干净、防止 migratetype 互相污染。这个策略的前提,是分配能发起 direct reclaim/compaction。
┌──────────────────────────────────┐
│ 碎片整理模式强制不碎片化分配 │
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 不可回收分配进慢路径后提前退出 │ 不碎片化标志仍置位,不尝试回退
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 明明其它迁移类型有空闲却直接失败 │ 网络重载下 skb 缓存大量分配失败
└──────────────────────────────────┘
但 GFP_ATOMIC 这类不可回收分配恰恰不能发起 direct reclaim/compaction,它们在中断或持锁上下文,不能睡眠等回收。在 defrag_mode=1 下,这类分配走到慢速路径的"不可直接回收"提前退出点时,不碎片化标志还挂着,于是它不尝试任何 fallback 就直接失败,哪怕其它迁移类型的 freelist 里明明有空闲页。
问题
- defrag_mode 强制不碎片化,优先回收而非回退
- 不可回收分配不能发起 direct reclaim
- 慢路径提前退出时不碎片化标志仍置位
- 其它迁移类型有空闲也不回退,直接失败
方案
核心是在那个提前退出点,对不可回收但请求了 kswapd 回收的分配放开不碎片化约束。
改动在"不可直接回收"退出处加判:若 defrag_mode 启用、不碎片化标志置位、且分配本身请求了 kswapd 异步回收(即它本指望 kswapd 回收、只是自己不能直接回收),就清掉不碎片化标志,让它能回退到其它迁移类型的 freelist。
旧:不碎片化标志未清,直接失败
┌────────────────────────────┐
│ 不可回收分配命中提前退出 │
└─────────────┬──────────────┘
▼
┌────────────────────────────┐
│ 不碎片化标志仍在,跳过回退 │ 其它迁移类型有空闲也用不上
└────────────────────────────┘
新:清标志后重试回退
┌──────────────────────────────────┐
│ 对请求 kswapd 回收的不可回收分配 │
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐ 纯投机分配(不请求 kswapd 回收)仍允许失败,
│ 清除不碎片化标志,允许跨类型回退 │ 避免碎片
└──────────────────────────────────┘
这个判据把"GFP_ATOMIC 这类"和"纯投机分配"区分开。GFP_TRANSHUGE_LIGHT 之类不请求 kswapd 回收的纯投机分配仍然允许失败:它们本就有合理的 fallback 路径,且不应为了自己而引入碎片。换言之,修复只救"本想回收、只是不能自己动手"的分配,不动"压根没要求回收"的投机分配。
收益
作者在采用 defrag_mode=1 的 Meta 生产服务上观察到(受载服务,2 小时窗口):
| 现象 | 数据 |
|---|---|
| SLUB 分配失败(skbuff_head_cache,GFP_ATOMIC) | 85,509 次 |
| 其它迁移类型 freelist 的空闲内存 | ~13 GB |
这些失败全发生在网络路径,实际表现为丢包、RPC 请求失败、尾延迟尖峰与整体服务降级:明明有约 13 GB 空闲,却因为不碎片化约束不肯回退而分配失败。修复后 GFP_ATOMIC 分配恢复回退能力,这些失败随之消除。