背景
高阶 GFP_ATOMIC 分配在 zone 处于 low/min 水位之间时,可能经 alloc_flags 放宽后由 get_page_from_freelist 重试,落到 PCP list 上以 ALLOC_HIGHATOMIC 服务。这种分配命中 PCP 后,会接着调一次 reserve_highatomic_pageblock,意图是给高阶原子预留扩容。
┌────────────────────────────────────┐
│ 高阶原子分配经 per-CPU 缓存命中 │
└─────────────────┬──────────────────┘
▼
┌────────────────────────────────────┐
│ 命中后每次都尝试扩高阶原子预留 │ 缓存里的页是不可移动型,永不是高阶原子型
└─────────────────┬──────────────────┘
▼
┌────────────────────────────────────┐
│ 每次新转一个块为高阶原子,块多空闲 │ 吃满 zone 上限,正常分配被挤、过早回收
└────────────────────────────────────┘
但这里有个隐藏前提没对上:PCP list 上的页是不可移动迁移类型(unmovable),而 MIGRATE_HIGHATOMIC 的页根本不出现在不可移动 PCP list 上。所以每次 reserve 都发现"这个页不是高阶原子型",于是每次都新转一个 pageblock 成 MIGRATE_HIGHATOMIC。
问题
- 高阶原子 PCP 命中后每次都扩高阶原子预留
- PCP 页是不可移动型,永不是高阶原子型
- 每次新转一个 pageblock,多数空闲
- 吃满 zone 1% 上限,挤占正常分配、过早触发回收
方案
核心是认清"能被 PCP 服务的高阶原子分配,说明还没到需要扩预留的程度",把扩预留挪到真正需要它的伙伴路径。
旧:per-CPU 缓存命中后每次扩预留
┌────────────────────────────┐
│ 缓存页不可移动,每次转新块 │
└─────────────┬──────────────┘
▼
┌────────────────────────────┐
│ 预留膨胀吃满上限,块多空闲 │
└────────────────────────────┘
新:扩预留只在伙伴路径
┌────────────────────────────┐
│ 缓存命中不扩预留 │
└─────────────┬──────────────┘
▼
┌────────────────────────────┐
│ 缓存空则回退伙伴,失败才扩 │ 预留回到正常水位,不挤正常分配
└────────────────────────────┘
具体地,把 highatomic reserve 的检查与扩展移到 buddy 路径;PCP 空时 ALLOC_HIGHATOMIC 不再 refill PCP,而是直接回退 rmqueue_buddy。在 rmqueue_buddy 里仍先试 MIGRATE_HIGHATOMIC 的 buddy list(与以前一样),只有它失败才扩展 reserve。这样 PCP 仍提供快速的高阶原子分配,但不再每次命中都无谓地扩预留。
收益
作者未提供量化 benchmark。从机制与真实场景推断的预期收益:
- 高阶原子预留不再被 PCP 命中无谓扩容,回到正常水位
- 不再有无故的(多数空闲)高阶原子 pageblock 吃掉 zone 1% 上限
- 正常分配不再被挤占,不再因此过早触发回收
作者在真实系统上观测到:突发内存活动配 GFP_ATOMIC SLUB(新 slab 用 GFP_ATOMIC、偶有低于 low 水位的 get_page_from_freelist 重试)会让 MIGRATE_ATOMIC pageblock 持续累积;修复后测试稳定,预留建到正常水位、无 highatomic 失败。