0/3 已展开

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-ID20260901124347.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.cscx_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_idlefor-7.4 上还没合入。

关键实现

  1. 入口cgroupfs_write(cpu.idle)scx_group_set_idle(tg, idle)
  2. 判断顺序:先看 scx_cgroup_enabled && sch && SCX_HAS_OP(...),再追加值变化判断——cgroup
    未启用或 BPF scheduler 未挂载时提前短路,避免无谓的 cgroup 字段访问。
  3. 比较源:比较的是 task_group 自身保存的 idle 状态,而不是 cgroupfs 文件的当前值;
    只有 task_group 持有"上次真正下发过的状态",与 weight 路径对 tg->scx.weight 的处理同源。
  4. 同步语义:判断本身仍在 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