sched-ext discussion
[PATCH v2] sched_ext: don't deliver duplicate ops.cgroup_set_idle() for same value
LLM 分析
sched_ext:避免重复下发 cgroup_set_idle 回调
系列概况
- 标题:[PATCH v2] sched_ext: don't deliver duplicate ops.cgroup_set_idle() for same value
- 作者:Tao Cui cuitao@kylinos.cn
- 版本:v2
- 规模:1 file changed, 2 insertions(+), 1 deletion(-)
- 修改文件:kernel/sched/ext/ext.c
- 代码统计:scx_group_set_idle() 中一行 if 条件扩展
- Message-ID:20260901124347.755904-1-cui.tao@linux.dev
- 完整性:完整单补丁(已被 Tejun Heo 应用到 sched_ext/for-7.3-fixes)
补丁目的
ops.cgroup_set_idle() 在文档里被约定为:cgroup 在 idle 与非 idle 之间发生切换时才下发。
但 scx_group_set_idle() 的内核侧实现是无条件下发——每次 cpu.idle 文件被写入都会触发一次
SCX_CALL_OP(sch, cgroup_set_idle, ...)。这会让基于"状态切换"做 toggle/记账的 BPF scheduler
把同一个值重复当作 transition 处理,计数错误。
补丁模仿已经具备 value-preserving 跳过的 scx_group_set_weight(),在调用前加一道
"值真变化才下发"的护栏,与 weight 路径语义对齐。
旧流程的问题
- 不比较新旧值,每次写
cpu.idle都直接下发一次 BPF 回调。 - 与
cgroup_set_idle()文档约定的 "transition" 语义不一致。 - toggle/记账类 BPF scheduler 会把同值重复识别为多次状态切换,统计失准。
- 与
scx_group_set_weight()的去重行为不对称——weight 路径早就不会重发,idle 路径却会。
新流程
- 在
scx_group_set_idle()的percpu_down_read之后追加判断:
tg->scx.idle != idle(在 for-7.4 之后会切回sched_idle)。 - 当新旧值相等时整段
SCX_CALL_OP被跳过,不下发 BPF 回调。 - 与
scx_group_set_weight()行为对称:都是只在实际变化时才下发。 for-next合并窗口到来时,字段 rename 与本补丁的比较会自动对齐。
Patch 概览
单文件改动,diff 集中在 kernel/sched/ext/ext.c 的 scx_group_set_idle():
void scx_group_set_idle(struct task_group *tg, bool idle)
{
percpu_down_read(&scx_cgroup_ops_rwsem);
sch = scx_tg_knob_sched(tg);
- if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle))
+ if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle) &&
+ tg->scx.sched_idle != idle)
SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle);
/* Update the task group's idle state */
...
}
Tejun 应用到 for-7.3-fixes 时把 tg->scx.sched_idle 改回 tg->scx.idle,因为本分支里
字段名是 idle,rename 到 sched_idle 在 for-7.4 上还没合入。
关键实现
- 入口:
cgroupfs_write(cpu.idle)→scx_group_set_idle(tg, idle)。 - 判断顺序:先看
scx_cgroup_enabled && sch && SCX_HAS_OP(...),再追加值变化判断——cgroup
未启用或 BPF scheduler 未挂载时提前短路,避免无谓的 cgroup 字段访问。 - 比较源:比较的是 task_group 自身保存的
idle状态,而不是 cgroupfs 文件的当前值;
只有 task_group 持有"上次真正下发过的状态",与 weight 路径对tg->scx.weight的处理同源。 - 同步语义:判断本身仍在
percpu_down_read之下,但与并发cgroup_init_idle()写者之间
没有显式互斥,这一点正是 Sashiko AI 标为 High 的 TOCTOU 风险所在(见 Highlight)。
类比
想象一栋公寓楼,物业只在大门从"锁住→打开"或"打开→锁住"时收到通知。
原先只要有人在门禁面板上按一次"开门",物业就被叫一次;如果同一个人又按一次同样的"开门",
物业又会被叫一次,保安以为自己白跑了一趟,账本上也被多记一次门禁事件。
补丁相当于让门卫在按下按钮前先瞥一眼门当前到底开没开——状态没变化就不打扰物业,
只在真正的"关→开"或"开→关"切换时才发通知,账本才不会被噪音污染。
Highlight:风险与注意点
- TOCTOU 风险(Sashiko AI 标 High):
scx_group_set_idle()读取tg->scx.idle时只持有
percpu_down_read(&scx_cgroup_ops_rwsem),并未与cgroup_init_idle()的写者互斥。并发
场景下 BPF 看到的"状态"与内核真实状态可能漂移,长期线上若仍观察到重复回调或记账异常,
需要把判断推到持锁写路径或在 init_idle 写者侧加同步。 - 字段名错位(Sashiko AI 标 Low):
tg->scx.sched_idle在当前for-7.3-fixes分支并不存在,
原样 patch 会编译失败;维护者 Tejun 在应用时手动替换为tg->scx.idle,rename 与本补丁
的同步切换被推迟到for-next合并窗口。 - 对称性盲点:weight 路径早就做过值去重,未来再增加任何
cgroup_set_xxx类回调时,
应默认带上同样的值变化护栏,避免再次出现"weight 不重发、idle 重发"这种不对称。 - API 兼容性:cgroupfs 接口语义不变(写
cpu.idle的最终态依然正确),只是 BPF 回调次数
减少;不依赖 transition 计数、只在 idle 端读值的 BPF scheduler 完全无感知。 - 审阅链路:维护者 Tejun Heo 当场 ack 并合入,AI 评审(Sashiko)抓住的两条问题里"编译失败"
已被人工兜底修复,TOCTOU 留给后续观察。
版本变化
- v1 → v2:仅修复
Link:tag 中缺失@linux.dev后缀的问题(由审阅者 Andrea Righi 在 v1 审阅
时指出),代码逻辑与本 v2 一致。
一句话总结
给 scx_group_set_idle() 加上"idle 值真的变化才下发 BPF 回调"的护栏,与 weight 路径对齐,
避免同值写入污染 BPF scheduler 的 transition 计数。
cgroupfs write cpu.idle
|
v
scx_group_set_idle(tg, idle)
|
v
percpu_down_read(&scx_cgroup_ops_rwsem)
|
v
sch = scx_tg_knob_sched(tg)
|
v
+--[ enabled && sch && has_op ]--no--> skip (no scheduler / no op)
|
yes
|
v
+--[ tg->scx.idle != idle ]--no--> skip (no transition)
|
yes
|
v
SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle)
|
v
update tg->scx.idle = idle