0/5 已展开

LLM 分析

sched_ext:Dispatch path follow-ups

系列概况

  • 标题: [PATCHSET sched_ext/for-7.3] sched_ext: Dispatch path follow-ups
  • 作者: Tejun Heo tj@kernel.org
  • 版本: 基于 sched_ext/for-7.3 (e72979d3264a),无 vN→vN+1 演进
  • 规模: 3 个补丁;7 个文件;+54 / −46
  • 修改文件:
    • kernel/sched/ext/ext.c
    • kernel/sched/ext/inlines.h
    • kernel/sched/ext/sub.c
    • kernel/sched/sched.h
    • tools/sched_ext/include/scx/enum_defs.autogen.h
    • tools/sched_ext/include/scx/enums.autogen.bpf.h
    • tools/sched_ext/include/scx/enums.autogen.h
  • 代码统计: +54 / −46
  • Message-ID: 20260815010532.3663253-1-tj@kernel.org
  • 完整性: 完整;已被 maintainer Tejun Heo 应用到 sched_ext/for-7.3(回复 b63b856f7725bc14f2fe4dcece0022fc@kernel.org

补丁目的

本系列是对 4c95380701f5 ("sched/ext: Fold balance_scx() into pick_task_scx()") 这次 dispatch path rework 的三轮收尾:

  1. 0001 修一个潜在死锁:dispatch 在持锁期间可以 drop rq lock,queued 的 kick_sync_wait_bal_cb 会被其他持锁者 release 时 flush 到外 CPU 上跑,跨 CPU 比对无关的 percpu 快照可能导致永远等不到自家同步位。
  2. 0002 删一段历史兜底:verdict 现在直接以返回值传递,不再有 SCX_RQ_BAL_KEEP 标志失效的风险,因此 SCX_DSP_PREV → SCX_DSP_LOCAL 的降级检查已过时。
  3. 0003 改名对齐:balance_one() → dispatch_one()SCX_RQ_IN_BALANCE → SCX_RQ_IN_DISPATCH,让符号反映实际行为。

旧流程的问题

0001 旧假设kick_sync_wait_bal_cb 假设自己一定在 rq 所属 CPU 上运行——它对比的 cpus_to_sync 位于该 CPU 的 percpu 区,busy-wait 也在 rq lock drop + IRQ enable 状态下进行。dispatch rework 之后这个假设不再成立:sched class change 路径、scx task iterator 等 rq lock 持有者在 drop 期间 release 时会 flush pending balance callback,把 callback 调度到执行 CPU 上去跑。该 CPU 的 percpu 快照与目标 rq 毫无关系——如果本 CPU 自己正好是等待目标,就会出现"自己等自己"的死锁。

0002 旧逻辑if (verdict == SCX_DSP_PREV && prev->sched_class != &ext_sched_class) verdict = SCX_DSP_LOCAL; 这段 fixup 防御的是 balancing 和 picking 分离、SCX_RQ_BAL_KEEP 标志可能 stale 的远古场景。verdict 现在作为返回值在同一次调用里"创建→消费",且每个 keep 决策都在 rq lock 下检查 SCX_TASK_QUEUED——class 切换会先 dequeue,imply ext_sched_class。所以这段代码是历史包袱。

0003 旧命名balance_one()SCX_RQ_IN_BALANCEsched_class->balance() 时代的产物。现在 sched_class->balance() 已经被移除,函数实际就是 dispatch;名字和职责已经错位。

新流程

  • 0001put_prev_task_scx() 入口先检查 cpu_of(rq) != smp_processor_id(),是的话直接 return;让 rq 真正的 CPU 在自己的下一次 __schedule() tail 里跑这段逻辑,期间由 resched kick 推进等待目标。
  • 0002:从 dispatch_pick()dispatch_core_pick() 删除那两段降级代码及其 WARN_ON_ONCE
  • 0003balance_one() → dispatch_one()SCX_RQ_IN_BALANCE → SCX_RQ_IN_DISPATCH,同步注释;自动生成 BPF 头保留旧名做零填充以兼容。

Patch 概览

0001 — kick_sync_wait_bal_cb foreign-CPU 防护

文件:kernel/sched/ext/ext.c (+17 / −2)。关键 hunk:

static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
                              struct rq_flags *rf)
{
    struct scx_kick_syncs __rcu *ks;
    unsigned long *ksyncs;

    /*
     * This callback is queued and normally flushed within @rq's own
     * scheduling pass. However, dispatch can drop the rq lock while it sits
     * queued, and lock takers in that window (the sched class change paths,
     * the scx task iterator) flush pending balance callbacks on release,
     * running this one on a foreign CPU whose snapshots are unrelated. The
     * kicked CPUs are already on their way to advance the kick_syncs being
     * waited on. Don't get in the way.
     */
    if (unlikely(cpu_of(rq) != smp_processor_id()))
        return;

    ks = __this_cpu_read(scx_kick_syncs);
    ksyncs = rcu_dereference_sched(ks)->syncs;
    ...
}

Fixes: 4c95380701f5Cc: stable@vger.kernel.org # v6.19+

0002 — 删除 keep_prev fixup

文件:kernel/sched/ext/ext.c (−12)。删除 dispatch_pick()dispatch_core_pick() 中:

if (unlikely(verdict == SCX_DSP_PREV &&
             prev->sched_class != &ext_sched_class)) {
    WARN_ON_ONCE(scx_enable_state() == SCX_ENABLED);
    verdict = SCX_DSP_LOCAL;
}

0003 — 重命名 balance → dispatch

机械改名:balance_one() → dispatch_one()SCX_RQ_IN_BALANCE → SCX_RQ_IN_DISPATCH,注释与文档同步;自动生成的 BPF enum 头保留旧名做零填充。

关键实现

修复真正生效的是 0001:

  • 触发链:dispatch 持锁期间 drop rq lock → 任意 lock 持有者(sched class change / scx task iterator)在 release 时调用 flush_balance_callbacks() → queued 的 kick_sync_wait_bal_cb 被搬到执行 CPU 上运行。
  • 拒绝点put_prev_task_scx() 的最早入口。
  • 拒绝语义:直接放弃,不去尝试"修复"跨 CPU 的语义——跨 CPU 时 percpu 快照根本不是同一份,比对没有意义。
  • 后续路径:rq 真正的 CPU 在下一次 __schedule() tail 里仍然会调用真正的 put_prev_task_scx(),完成 kick_sync 等待;期间的 cpus_to_sync 位由 resched kick 持续推进。

0002/0003 是同一主题的衍生清理:0002 删除"verdict 路径可能 stale"的兜底,0003 把代码本身的名字同步对齐。

类比

把 rq 想象成某家公司的一间会议室,会议室里有一块白板记录"哪些同事需要等我点头"。kick_sync_wait_bal_cb 就是"去会议室门口确认白板"这个动作。

老规矩:"确认白板"必须由会议室主人亲自做。dispatch 改造后,主人中途可能离开会议室去走廊接电话(drop rq lock),这时过路同事(lock holder)看到门口堆了待办就顺手"帮忙确认白板"——但他们跑去的是自家会议室门口,看的是完全不同的白板,可能永远等不到自家会议室里该完成的事。

0001 的修法:门口的人发现自己不是这间会议室的主人,直接放弃走开,让主人回来时自己处理。0002 是删掉一段历史遗留的兜底检查(现在每次都先确认主人是不是同一个人,多余了)。0003 是把工牌上的"balance 部门"标签换成"dispatch 部门",让组织结构名实相符。

+-------------------+      drop rq lock       +-------------------+
| rq CPU (owner)    | ----------------------> | other CPU (locker) |
| - holds rq lock   |                          | - takes rq lock    |
| - queues bal_cb   |                          | - flushes bal_cb   |
+-------------------+                          |   on release       |
        ^                                       +-------------------+
        |                                                  |
        |                          put_prev_task_scx on foreign CPU
        |                                                  v
        |                                       +-------------------+
        |                                       | if cpu_of(rq) !=   |
        |                                       |    smp_processor() |
        |                                       |   -> return early |
        |                                       +-------------------+
        |
        |  next __schedule() tail on the rq CPU
        |  runs real put_prev_task_scx()
        v
+-------------------+
| real kick_sync    |
| wait completes    |
+-------------------+

Highlight:风险与注意点

  • 跨 CPU 走的是"放弃"路径,不是"修复"路径:percpu 快照根本无法在不同 CPU 之间对齐;任何后续测试如果再次出现 kick_sync hang,应优先怀疑有其他持锁释放点也在 drop 锁期间被 flush。
  • stable CC v6.19+:说明这是 6.19 才引入的 regression,使用 for-6.19 分支的 stable 维护者需要 backport 0001。
  • 自动生成 BPF 头保留旧 SCX_RQ_IN_BALANCE:是为了加载时零填充兼容;后续任何清理这个旧条目的 patch 都要先确认 BPF skeleton 已停止引用。
  • 0002 删掉的 WARN_ON_ONCE:原本是历史诊断信号;未来若再次出现 SCX_DSP_PREV 路径上的非 ext 任务,可能不会再有可见告警,需要靠回归测试覆盖。
  • 0003 是纯 rename:没有任何功能性变化,code review 应关注 BPF skeleton / 用户态工具是否还有引用 balance_one / SCX_RQ_IN_BALANCE 的旧符号。

版本变化

单版本 patchset,无 vN → vN+1 演进;git branch:git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git coresched-followups

一句话总结

本系列修复了 sched_ext dispatch 路径在 drop rq lock 窗口被外 CPU 误触发 kick_sync_wait_bal_cb 的潜在死锁,并清理因此而过时的 keep_prev 兜底与 balance-era 命名。