mempool_alloc()
I/O 路径的分配入口
第一遍:轻量 GFP
去掉 DIRECT_RECLAIM
pool->alloc(gfp_temp)
先试底层 slab
mempool_adjust_gfp()
gfp_mask |= __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN;
return *gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_IO);
第一遍去掉 DIRECT_RECLAIM;若原 gfp 有 KSWAPD,仍可自旋
失败后
gfp_temp = gfp_mask;
恢复原 gfp 再试 pool->alloc()
失败路径:预留池 / 等待
第一遍可能不阻塞
第一遍“不阻塞”,但并没有禁止自旋
mempool 的轻量重试,与 sheaf 的 refill 门控在这里交叉
per-CPU sheaves
main
对象从这里弹出
spare
备用空 / 满链
alloc_from_pcs()
main 有对象:直接返回
main 空:replace_empty_main
旧门控:refill 需能阻塞
node barn
full sheaves
empty sheaves
barn_replace_empty_sheaf()
allow_spin ? spin_lock : trylock
命中就换链,不碰 refill
__pcs_replace_empty_main()
spare 有货?
barn 有 full?
旧 blocking?
新 allow_spin?
refill / alloc
回到快路径
前两步是快速命中;旧看 blocking,新看 allow_spin
关键:barn 命中本来就能自旋;barn 落空后的 refill 才被旧门控挡住
旧代码(节选):barn 落空后看 blocking
full = barn_replace_empty_sheaf(barn, pcs->main,
gfpflags_allow_spinning(gfp));
if (full) return pcs;
can_alloc = gfpflags_allow_blocking(gfp);
if (!can_alloc) return NULL;
/* 只有 can_alloc 为真才 refill */
命中 spare / barn 仍可成功
同一个 gfp,两个判断
gfp
blocking
spinning
GFP_KERNEL
是
是
GFP_NOWAIT
否
是
mempool 第一遍*
否
是
差在 DIRECT_RECLAIM;* 以 GFP_KERNEL 为例
此时 KSWAPD 还在,第一遍可自旋
快路径失败:main 空 -> barn 空 -> 门控为假
main 空
barn 空
blocking 否
return NULL
___slab_alloc
ublk null target 的 profile:alloc_from_pcs() 失败,回退慢路径
门控问错了问题:能否睡眠 ≠ 能否自旋
补丁(节选):换成 gfpflags_allow_spinning
- can_alloc = gfpflags_allow_blocking(gfp);
+ allow_spin = gfpflags_allow_spinning(gfp);
- if (can_alloc) {
+ if (allow_spin) {
refill_sheaf(s, empty, gfp | ...);
refill 只需要能自旋,不需要能睡眠
gfpflags_allow_spinning()
return !!(gfp_flags & __GFP_RECLAIM);
__GFP_RECLAIM =
__GFP_DIRECT_RECLAIM
| __GFP_KSWAPD_RECLAIM
GFP_NOWAIT 含 KSWAPD -> true
alloc_pages_nolock() 两者都无 -> false
对 mempool 第一遍的效果
gfp_temp
allow_spin = true
refill_sheaf()
alloc_from_pcs()
继续快路径
不再因为“不阻塞”而直接回退慢路径
一句话:把“能不能睡”换成“能不能自旋”
为什么安全:refill 不要求睡眠
搬对象只用 spinlock;新 slab 按 gfp 分配,不额外要求睡眠
refill_sheaf() / refill_objects()(节选)
empty sheaf
refill_objects()
partial / new slab
WARN_ON_ONCE(!gfpflags_allow_spinning(gfp));
refill_objects(...) // allow_spin = true
新 slab 路径也只用 spinlock / trylock
refill 前后重新拿锁(节选)
local_unlock(&s->cpu_sheaves->lock);
pcs = NULL; // 防止返回失效指针
refill_sheaf(...);
if (!local_trylock(...)) goto barn_put;
pcs = this_cpu_ptr(s->cpu_sheaves);
trylock 失败:full sheaf 还回 barn
成功:pcs 指向当前 CPU 的 sheaf
GFP_NOWAIT 下没有睡眠点
new_slab 不做直接回收;trylock 失败就把 full sheaf 还回 barn
未解决:memoryless nodes
sheaves 依赖本节点的 barn / partial 列表
无内存节点没有本地 slab 来源
refill 门控修好,快路径仍可能失效
ublk 回归的另一个组件
作者:另行处理
memoryless node
CPU
本地 node
无内存
分配只能去远端节点
sheaf 的本地性前提失效
边界:解决什么,不解决什么
解决:mempool 等轻量 GFP 调用的 refill
不解决:memoryless nodes 上的 sheaf 失效
其他调用者:只要 allow_spinning 为真,同样受益
诚实结论:ublk 回归只解掉 sheaf 这一半
旧:快路径失败
main 空
refill 被挡
慢路径
alloc_from_pcs() 失败 -> ___slab_alloc()
ublk null target profile
新:refill 后继续
main 空
allow_spin
refill
refill 成功 -> alloc_from_pcs() 继续
快路径恢复
预期收益
减少 ___slab_alloc() 回退
降低分配开销,I/O 负载恢复 sheaf 快路径
作者未提供量化 benchmark;这是机制修复
不是吞吐数字的胜利,而是快路径不再被错误门控挡住
ublk null target 的回归,先修 sheaf 这一半
① 问题
mempool 轻量 GFP
门控却看 blocking
② 修复
改看 allow_spinning
refill 可自旋
③ 边界
memoryless nodes
另行处理
旧
blocking = false
-> 不 refill
-> ___slab_alloc()
新
spinning = true
-> refill
-> alloc_from_pcs()
一个条件,恢复一条快路径
allow_spinning 对齐 mempool 与 sheaf
memoryless nodes 仍待另一组补丁
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x