0/3 已展开

LLM 分析

sched_ext:禁止向已禁用任务回调 set_weight

系列概况

  • 标题:[PATCH sched_ext/for-7.3] sched_ext: skip ops.set_weight() for disabled tasks
  • 作者:Kuba Piecuch <jpiecuch@google.com>
  • 版本:初始投稿(无显式 vN)
  • 规模:1 个实际 patch
  • 修改文件:kernel/sched/ext/ext.c(1 个文件)
  • 代码统计:+11/-0
  • Message-ID:20260710144342.3802587-1-jpiecuch@google.com
  • 完整性:b4 找到 3 封邮件和 1/1 个 patch,无缺失警告、无 tip-bot2 污染;含 Sashiko 自动审查和 Tejun Heo 的应用回复

一句话总结:任务离开 SCX 后不再收到 ops.set_weight(),避免 BPF 调度器访问已经在 ops.disable() 中清理的任务状态。

补丁目的

任务从 sched_ext 切换到其他 sched_class 时,调度类切换框架先执行
switched_from_scx(),其中 scx_disable_task() 调用 ops.disable();随后更新
调度参数又进入 reweight_task_scx(),反而调用 ops.set_weight()

这破坏了 SCX 生命周期约定:ops.disable() 表示 BPF 调度器可以忘掉该任务,
其后应当只可能退出任务或重新启用,而不应继续更新权重。补丁在权重回调前检查
任务状态,只处理 SCX_TASK_ENABLED 任务。

旧流程的问题

回归来源是 637b0682821bsched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern)。
该提交把 switched_from() 提前到调度参数更新之前;对 SCX 而言,这等价于把
ops.disable() 移到了 reweight_task_scx() 之前。

旧路径的关键顺序是:

  1. sched_change_begin() 调用 switched_from_scx()
  2. scx_disable_task() 调用 BPF ops.disable(p),随后状态转为 SCX_TASK_READY
  3. __setscheduler_params()set_load_weight() 调用旧调度类的 reweight_task_scx()
  4. 后者仍更新 p->scx.weight 并调用 ops.set_weight(p)
  5. 最后才把 p->sched_class 改为新调度类。

若 BPF 调度器在 ops.disable() 中删除哈希表、队列或其他 bookkeeping,第 4 步
可能访问已不存在的状态;即使具体调度器碰巧容忍,也违反回调接口语义。

新流程

任务离开 SCX
    |
    v
scx_disable_task() --> ops.disable() --> state = SCX_TASK_READY
    |
    v
set_load_weight() --> reweight_task_scx()
                          |
                          v
                 state == SCX_TASK_ENABLED ?
                    /                  
                  是                    否
                  |                     |
                  v                     v
        更新 scx.weight 并回调          直接返回
             ops.set_weight()

旧:只排除已死亡且完成清理的任务,禁用后的 READY 任务仍会收到权重回调。

新:在计算 p->scx.weight 前要求状态为 SCX_TASK_ENABLED;离开 SCX 的任务
直接返回。如果以后重新加入,__scx_enable_task() 会依据当前优先级重算权重,
并在启用路径通知 ops.set_weight(),因此不会永久丢失更新。

关键实现

1. 状态检查同时保护数据和回调

if (scx_get_task_state(p) != SCX_TASK_ENABLED)
	return;

检查位于 p->scx.weight 赋值之前,所以既不改写已禁用任务的 SCX 权重,也不
进入 BPF 回调。这里依赖调用者已持有 rq lock;函数原有的
lockdep_assert_rq_held() 保留不变。

2. 状态转换提供判定依据

scx_disable_task() 在调用 ops.disable() 后把状态从 ENABLED 改成 READY
所以后续 reweight_task_scx() 能稳定识别任务已离开 SCX。整个切换路径持有任务
和 runqueue 所需锁,补丁没有增加新锁、RCU 操作或睡眠点。

3. 重入 SCX 时恢复最新权重

__scx_enable_task() 根据 static_prio(idle 策略另行处理)重新计算
p->scx.weight,再调用 ops.enable()ops.set_weight();外层随后设置
SCX_TASK_ENABLED。所以本补丁跳过的是错误生命周期阶段的通知,而不是取消权重语义。

4. 投稿与应用版本需区分

原始投稿目标写作 sched_ext/for-7.3。Tejun Heo 回复称实际应用到
sched_ext/for-7.2-fixes,并补充:

  • Fixes: 637b0682821b (...)
  • Cc: stable@vger.kernel.org # v6.19+

b4 am 生成的补丁包含经 DKIM 验证的 Fixes trailer,代码 diff 未变化。当前
本地 master 能找到回归提交,但尚未出现这 11 行保护,不能据维护者回复推断当前
检出已合入修复。

类比

把 BPF 调度器想成会员系统:ops.disable() 是注销会员并删除档案,
ops.set_weight() 是修改会员等级。旧流程先注销、再要求修改等级;补丁让前台先
检查会员仍有效。若用户以后重新注册,系统会按最新信息重新建立等级。

Highlight:风险与注意点

  • 修复只改变非 SCX_TASK_ENABLED 路径;正常启用任务的权重更新不变,热路径仅多一次状态判断。
  • 提前返回也跳过 p->scx.weight 更新,但重新启用路径会重算,因此生命周期闭合。
  • Sashiko 提出 reweight_task_scx() 未同步 p->se.load,可能令转回 CFS 的 load weight 陈旧;其明确标为既有问题,不是本补丁引入,也没有人工评审结论支持或否定。
  • Tejun 明确认定这是 v6.19 起的回归并应用到 fixes 分支;线程没有其他人工 review。自动审查回复不能等同于维护者对旁支问题的认可。
  • stable 回传需核对各分支的文件迁移和调用顺序;当前主线文件为 kernel/sched/ext/ext.c,较早基线使用 kernel/sched/ext.c

版本变化

线程没有 v2/v3。唯一重要变化发生在维护者处理阶段:投稿标注面向 7.3,最终被
视为 7.2 fixes 和 v6.19+ stable 回归修复,并追加 Fixes 与 stable Cc;补丁逻辑
本身没有修订。

一句话总结

该补丁用一个 SCX 状态门禁恢复任务生命周期顺序:禁用后不再回调权重更新,重新
启用时再按最新优先级重建权重,以极小改动消除 BPF 调度器的失效状态访问窗口。