0/3 已展开

LLM 分析

sched_ext:cgroup 迁移回调 rehoming 死亡任务 race 的驳回讨论

系列概况

  • 标题[PATCH] sched_ext: don't rehome a dead task in scx_cgroup_task_migrated
  • 作者:Tao Cui cuitao@kylinos.cn
  • 版本:单 patch,无版本号([PATCH]
  • 规模:1 file changed, 7 insertions(+)
  • 修改文件kernel/sched/ext/sub.c
  • 代码统计:新增 7 行(SCX_TASK_DEAD 检查 + 提前返回 + task_rq_unlock
  • Message-ID20260811103122.357067-1-cui.tao@linux.dev
  • 完整性:完整(含 Fixes: tag 与 Signed-off-by),但已被 maintainer 在第 2 封邮件驳回,第 3 封作者本人认错,整条 patch 预计不会被合并。

补丁目的

scx_cgroup_task_migrated() 是 cgroup 迁移完成时通知 sched_ext 的回调。补丁想在这里加一个守卫:当任务已经被 sched_ext_dead() 标记为 SCX_TASK_DEAD 时,跳过 scx_rehome_task(),避免把死亡任务重新挂到新 cgroup 后泄漏 BPF 调度器的 per-task 资源(cgrpaux 等)。

简单说:作者以为「cgroup 迁移 commit 和 MIGRATED 回调之间存在窗口,任务可能在这期间退出、被错误唤醒并重新启用」,所以想堵这个窗口。

关键实现

补丁 diff 关键内容:

static void scx_cgroup_task_migrated(struct cgroup_task_migrate_ctx *ctx)
{
    ...
+   if (scx_get_task_state(p) == SCX_TASK_DEAD) {
+       /* sched_ext_dead() raced us */
+       task_rq_unlock(rq, p, &rf);
+       return;
+   }
    scx_rehome_task(to, p);
    ...
}

逻辑是:拿住 rq 锁之后先看任务是否已走到 sched_ext_dead(),是的话直接放锁返回。作者注释写的是 sched_ext_dead() raced us

Fixes: bf9dee58ab56 ("sched_ext: Re-home tasks on cgroup migration") 指明这条修复针对引入 scx_rehome_task() 到 cgroup 迁移路径的那个 commit。

讨论走向(驳回)

Maintainer Tejun Heo 在第 2 封回复里给出非常具体的锁顺序分析:

  • SCX_TASK_DEAD 只在 finish_task_switch() -> sched_ext_dead() 里设置;
  • finish_task_switch() 之前必经 exit_signals(),而 exit_signals()cgroup_threadgroup_change_begin() 内拿 threadgroup rwsem;
  • MIGRATED 通知由 cgroup_migrate_execute() 执行,全程 cgroup_attach_lock() write-held 同一把 rwsem;
  • 因此迁移集合内的任务在迁移完成前根本走不到 exit 路径;已在退出的任务在更早的 cgroup_migrate_add_task() 阶段就被 PF_EXITING 检查过滤掉了;
  • 作者引用的 DEAD 检查都在 scx_task_iter 那些不走 threadgroup rwsem 的遍历里,那是另一个语境。

Tejun 还附带流程建议:code review 发现疑似 bug,先尝试 repro 再写补丁。

第 3 封作者直接认错:You're right, I missed that. I walked it again, a task past exit_signals() is filtered

类比

把 cgroup 迁移回调路径想象成一次「集体搬迁」:

  • cgroup_attach_lock() 是小区大门锁,写锁期间整个小区只允许「搬家工人」(迁移回调)进出;
  • 任何想「出门」(进入 exit 流程)的住户都得先到门卫那里登记拿 threadgroup rwsem;
  • 因为大门一直被搬家工人占着,exit_signals() 根本进不来;
  • 「已经在门卫那里排队等出门」的住户,在 cgroup_migrate_add_task() 阶段就被认出「你这人已经在走 exit 流程了,别混进搬家队伍」过滤掉。

所以「搬家工人(MIGRATED 回调)」碰到一个真正死亡的任务这件事,在锁结构下不会发生。补丁作者相当于在完全关着的小区里又装了一道门——保险,但不是必需。

ASCII 锁顺序图

                  cgroup_migrate_add_task()
                              |
                              v
                    PF_EXITING test -> filter dying tasks
                              |
                              v
                cgroup_threadgroup_change_begin()
                cgroup_attach_lock()  (write-held rwsem)
                              |
                              v
              +------ cgroup_migrate_execute() --------+
              |   MIGRATED notifiers                   |
              |     +-- scx_cgroup_task_migrated()     |
              |     |    +-- task_rq_lock(p, &rf)      |
              |     |    +-- (proposed) DEAD check?    |
              |     |    +-- scx_rehome_task(to, p)    |
              +----------------------------------------+
                              |
                              v
                cgroup_attach_unlock()
                cgroup_threadgroup_change_end()
                              |
                              v
                    task can now enter exit_signals()
                              |
                              v
                    finish_task_switch()
                              |
                              v
                    sched_ext_dead() -> SCX_TASK_DEAD

作者宣称的窗口 vs 真实锁顺序对比:

  Author-claimed window (does NOT actually exist):
  ----------------------------------------------------
  migration commit  --+
                      |  <-- supposed gap where task exits
  MIGRATED callback  --+

  Reality (lock ordering):
  ----------------------------------------------------
  attach_lock(write)  +==============================+
                      |  MIGRATED callbacks          |
                      +==============================+
                               ^
                               | tasks in set cannot
                               | enter exit_signals()
                               v
                      attach_lock release
  ----------------------------------------------------
                      then exit_signals() possible

Highlight:风险与注意点

  1. 盲目模仿其他调用点的检查模式:作者以「其他 scx_rehome_task() 调用点都检查 DEAD,这里也加上」为由添加检查,但其他调用点(典型是 scx_task_iter_* 遍历)运行在完全不同的锁上下文里,迁移回调处于 attach_lock write-held 下,DEAD 路径根本进不来。复制模式前要先看上下文是否一致,不能照搬。
  2. 流程教训:Maintainer 明确点出「code review 发现疑似 bug -> 先尝试 repro -> 再写补丁」。涉及 lock/race 的修复,没有 repro 容易把不存在的窗口当成真问题。
  3. patch 状态:截至本 thread 最后,作者承认分析错误,这条 patch 应当不会合并;下游 review/bisect 工作应在 lore 上先确认是否被撤回或重新提交。
  4. 后续观察点:真正值得继续留意的是 scx_task_iter_* 类「不走 threadgroup rwsem」的遍历路径是否需要 DEAD 跳过 rehome——那才是补丁作者最初类比的真实语境。
  5. Fixes: tag 准确性:若补丁最终被丢弃,Fixes: bf9dee58ab56 这条 tag 也不应传播到 stable 队列。

与其他相关 patch 系列的关联

  • bf9dee58ab56("sched_ext: Re-home tasks on cgroup migration"):本 patch 的 Fixes: 指向对象,向 cgroup migration 路径引入了 scx_rehome_task()
  • scx_task_iter_* 遍历系列:作者类比中那些已有的 DEAD 检查所在路径;与本 thread 不同的锁上下文,未在本 thread 中重新讨论。

一句话总结

这是一条原本想堵「cgroup 迁移 MIGRATED 回调里 rehoming 已死亡任务」race 的单文件补丁,被 maintainer 用 cgroup_attach_lockexit_signals() 的锁顺序论证驳回,作者本人在第 3 封回复中认错,patch 大概率不会合并。