背景
伙伴分配器为高优先级的非阻塞分配预留了一类专属空闲页,迁移类型记为 MIGRATE_HIGHATOMIC,由 ALLOC_HIGHATOMIC 分配标志开启。这个标志同时承担两项职责:
- 允许从已有的 highatomic 空闲页里分配("用已有预留")
- 允许把一整个新 pageblock 划为 highatomic("增长预留")增长是侵入性的:它会把原本属于其他迁移类型的可用空间据为己有,因此整套标志被限制只在慢路径使用,快路径完全不碰。
┌──────────────────────────────────────────┐
│ ALLOC_HIGHATOMIC 把两项职责绑在一起: │
│ 用已有预留:从 highatomic 空闲页分配 │
│ 增长预留:把新 pageblock 划为 highatomic │
└─────────────────────┬────────────────────┘
▼
┌──────────────────────────────────────────┐
│ 增长侵入性强,挤占其他迁移类型的 │
│ 可用空间,整套标志因此限于慢路径 │
└─────────────────────┬────────────────────┘
▼
┌──────────────────────────────────────────┐
│ 快路径完全不碰标志 │
│ 有 highatomic 空闲页也 fallback │
└──────────────────────────────────────────┘
但完全不碰标志,也留下了一道覆盖缺口:当 zone 里已经有 highatomic 空闲页时,一个高优先级的非阻塞分配到达快路径,却不会去用它们,反而 fallback 到其他迁移类型。这些预留本就是为高优先级的非阻塞分配准备的,快路径放着不用,等于漏掉了本该走的路。
问题
- 标志把"用已有预留"与"增长预留"两项职责绑在一起
- 增长侵入性强,整套标志被挡在快路径之外
- 快路径即使 zone 有 highatomic 空闲页也 fallback
- 高优先级的非阻塞分配拿不到本该用的预留
方案
症结在于两项职责被同一个标志绑死:增长这一半侵入性强,把标志挡在快路径之外,连带"用已有预留"也用不上。
修复的思路是把两项职责在运行时拆开(增长只许在慢路径发生,消费已有预留则下放到快路径),而不动标志位本身。
把增长关进慢路径
增长是一项侵入性的后处理:一次 highatomic 分配成功后,分配器会把刚拿到的整块 pageblock 标成 highatomic,原本属于其他迁移类型的可用空间就此被占用。旧实现里,只要 highatomic 分配成功,就顺手触发这道扩张,不论它走的是快路径还是慢路径。修复给增长加了一道门槛:只有当分配已经退到慢路径、动用最低水位线(ALLOC_WMARK_MIN)时,才允许新增 highatomic 预留。最低水位线正是分配退入慢路径的信号,于是"用已有预留"与"增长预留"两项职责被这道水位门槛隔开,无须引入新的标志位。
改动前:增长只看标志
┌──────────┐ ──▶ ┌──────────────┐ ──▶ ┌──────────┐ 快慢路径都增长
│ 分配成功 │ │ 仅看标志 │ │ 增长预留 │
└──────────┘ └──────────────┘ └──────────┘
改动后:增长还要慢路径把关
┌──────────┐ ──▶ ┌──────────────┐ ──▶ ┌──────────┐ 快路径只消费不增长
│ 分配成功 │ │ 标志+慢路径 │ │ 增长预留 │
└──────────┘ └──────────────┘ └──────────┘
让快路径认领已有预留
门槛一旦卡住增长,快路径上启用 highatomic 标志就只剩"消费已有预留"这一层含义,不会再触发扩张,因此可以安全下放。不过并非任何非阻塞分配都够资格消费这层预留:它本是为真正的紧急非阻塞准备的,启用标志前要先满足三个条件:分配请求不能要求直接回收,否则它本就有回旋余地,算不上紧急;分配请求还必须愿意借用预留,那些声明了"不占用预留"的请求已主动放弃这层保障,自然排除在外;在此之上还必须是高阶(order 大于 0)且带高优先级标记的分配。满足这三条的分配,才会在快路径首次尝试前启用 highatomic 标志,直接消费 zone 里已有的 highatomic 空闲页,而不必 fallback 到其他迁移类型。
非阻塞分配进入快路径,依次判定能否启用 highatomic 标志:
否
不要求直接回收? ─────▶ 不启用(有回旋余地,非紧急)
│ 是
▼ 否
愿意借用预留? ─────▶ 不启用(已声明不借用预留)
│ 是
▼ 否
高阶且高优先级? ─────▶ 不启用(单页或普通优先级)
│ 是
▼
启用标志:消费已有 highatomic 预留
这套判定并非新造。它原本埋在通用的分配标志翻译函数里,和慢路径的其他逻辑混在一起,快路径取不到。本系列把它单独抽成一个非阻塞判定函数,使快路径在首次尝试前就能直接调用、拿到结果。与此前已合入的 do not increase highatomic reserve after pcp alloc(Frank van der Linden)相衔接:那条改动把预留增长的决策集中到 buddy 路径、止住 PCP 路径上的无谓膨胀;本条则让快路径用上已有预留,并把增长进一步限定在慢路径。两者解决的问题不同、作者不同。
收益
作者在一台目标 zone 已备好 highatomic 空闲页的机器上跑了 UDP receive 负载,观测到如下定性行为变化:
- 改动前:负载不消费这些 highatomic 空闲页
- 改动后:符合条件的 order-1 分配(走 buddy 路径)消费了已有的 highatomic 空闲页
- 全程无 highatomic miss(预留未被消耗到空)
- 预留没有增长(快路径只用不增)
- NAPI page-frag 分配保持健康(无失败、无 order-0 fallback)
作者未给出吞吐或延迟的定量 benchmark。收益建立在机制论述(用上本就该用的预留)与上述定性行为验证之上。