sched-ext discussion
[PATCH] sched_ext/scx_flatcg: expire cached hweights on weight changes
LLM 分析
sched_ext/scx_flatcg:让权重变化真正生效
系列概况
- 标题:[PATCH] sched_ext/scx_flatcg: expire cached hweights on weight changes
- 作者:Tao Cui cuitao@kylinos.cn
- 版本:单版本(v1,单 patch)
- 规模:1 file changed, 3 insertions(+)
- 修改文件:
tools/sched_ext/scx_flatcg.bpf.c - 代码统计:3 行新增
- Message-ID:20260814144116.2767304-1-cui.tao@linux.dev
- 完整性:完整 PATCH,含 commit message、SoB、diff 与验证说明;已被 Tejun Heo 拣入
sched_ext/for-7.3
补丁目的
scx_flatcg 的 fcg_cgroup_set_weight() 在更新 cgc->weight 与父节点 child_weight_sum 时,没有让缓存的层级权重(hierarchical weight,hweight)失效。
对于永远没有 0→n 可运行状态跃迁的"持续忙"cgroup,原本只在任务激活时才推进的 hweight_gen 永远不会更新,导致 cgrp_refresh_hweight() 一直沿用旧的层级权重参与调度分配——cpu.weight 的修改对调度完全不可见。
修复方法是在权重写入后显式推进 hweight_gen,让下一次 refresh 用新权重重算 hweight。
旧流程的问题
user writes cgroupfs: cpu.weight 100 -> 800
|
v
fcg_cgroup_set_weight()
- update cgc->weight
- update parent pcgc->child_weight_sum
- unlock cgv_tree_lock
|
v
hweight_gen unchanged
|
v
next cgrp_refresh_hweight() sees gen matches
|
v
keeps old hweight; 800:100 ratio never takes effect
只有当某个 task 进入 0→n runnable 跃迁时,激活路径才会推进 hweight_gen。如果 cgroup 内所有任务都已经常驻 CPU 上运行(典型 CPU-bound 负载),这条"逃生通道"永远不被触发,权重改动永远传不到调度器。
新流程
fcg_cgroup_set_weight()
- update cgc->weight
- update parent pcgc->child_weight_sum
- unlock
+ __sync_fetch_and_add(&hweight_gen, 1) <-- new
|
v
next cgrp_refresh_hweight() sees gen mismatch
|
v
recompute hweight with new weight
|
v
new ratio takes effect immediately
关键实现
void BPF_STRUCT_OPS(fcg_cgroup_set_weight, struct cgroup *cgrp, u32 weight)
{
...
pcgc->child_weight_sum += (s64)weight - cgc->weight;
cgc->weight = weight;
bpf_spin_unlock(&cgv_tree_lock);
+ /* expire cached hweights so the new weight propagates */
+ __sync_fetch_and_add(&hweight_gen, 1);
}
关键点:
__sync_fetch_and_add保证 BPF 侧多 CPU 并发推进hweight_gen的原子性,与cgrp_refresh_hweight()里的比较/写入路径相对应。- 推进点放在
bpf_spin_unlock之后,避免把锁内临界区延长。 - 这是个
BPF_STRUCT_OPS回调,每次用户层cg_set_weight都会调用一次,开销可忽略。 hweight_gen推进后会令所有 cgrp节点的cached_gen != cur_gen,下一次 refresh 触发整树重算。
ASCII 图:刷新判定
cgrp_refresh_hweight(cgrp)
|
+-- read cgrp->hweight_gen (cached_gen)
|
+-- read global hweight_gen (cur_gen)
|
+-- if cached_gen == cur_gen:
| return cached hweight <-- old weight kept
|
+-- else:
recompute hweight from new weight_sum
write back cached_gen = cur_gen
return new hweight
只有当 hweight_gen 推进,cur_gen != cached_gen 才成立,刷新才会真正发生。
类比
想象一个图书馆有多个阅览室,每个阅览室门口挂着一块写有"接待时长比例"的牌子(hweight),牌子写好后放在那里很久没人更新(缓存)。
平时有读者进进出出(任务 0→n 激活),馆员会顺手把所有牌子刷新一次(gen 推进)。但如果有一个阅览室的读者一直在里面看书、没人出去再进来(持续忙碌的 cgroup),那块牌子就一直挂着旧数字。
新读者来问"我该去哪个阅览室",馆员只看牌子,就会把读者塞进老牌子上写的那个小房间。
补丁相当于:馆长直接下令"阅览室比例改了,所有牌子全部作废,请重写"。即使没人进出,下一次问路的读者也能拿到按新比例分配的阅览室。
Highlight:风险与注意点
__sync_fetch_and_add的 BPF 兼容性:scx 示例已经使用,惯例保持一致即可;后续若换 helper,需重新校验。- 推进时机:放在
bpf_spin_unlock之后,避免持锁推进;若有人后续把它移到锁内,要小心递归取cgv_tree_lock的死锁。 - Tejun 在回复中顺手指出的"另一只虫":
fcg_dispatch()的 true-up(__sync_fetch_and_add(&cgc->cvtime_delta, ...))看起来也有问题,属于另一个 bug 的伏笔,需要后续补丁。 - 持久忙 cgroup 之外的场景:补丁只补了"完全无跃迁"的极端情况;非极端场景下原本就能被任务激活路径覆盖,效果一致。
- 示例调度器定位:
scx_flatcg是tools/sched_ext/下的示例,并非主调度路径,影响面有限,但作为用户/开发者的参考实现,正确性同样重要。
Patch 概览
[PATCH 1/1] sched_ext/scx_flatcg: expire cached hweights on weight changes
file: tools/sched_ext/scx_flatcg.bpf.c
+3 lines in fcg_cgroup_set_weight()
function: after writing weight bump hweight_gen,
so cached hierarchical weight expires with write
版本变化
本系列只有 v1,无 v1→vN+1 演进。被 Tejun Heo 直接拣入 sched_ext/for-7.3,故 v1 即为最终版本。
与其他 patch 系列的关联
- 与 Tejun 在本帖第 5 封邮件里提到的
fcg_dispatch()true-up 问题属于同一文件的两处独立 bug,预计会有后续 patch 跟进修复cvtime_delta计算。 - 与
sched_ext整体的缓存一致性机制(hweight_gen单调推进)保持同一思路,可视为同一治理模式的延伸。
一句话总结
scx_flatcg 在改写 cgroup 权重时漏掉了"作废 hweight 缓存"的步骤,对持续忙碌的 cgroup 来说权重改动永远传不到调度器;只需在 fcg_cgroup_set_weight() 末尾原子推进 hweight_gen,下一次刷新就会按新权重重算。