sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Dispatch path follow-ups
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 的三轮收尾:
- 0001 修一个潜在死锁:dispatch 在持锁期间可以 drop rq lock,queued 的
kick_sync_wait_bal_cb会被其他持锁者 release 时 flush 到外 CPU 上跑,跨 CPU 比对无关的 percpu 快照可能导致永远等不到自家同步位。 - 0002 删一段历史兜底:verdict 现在直接以返回值传递,不再有
SCX_RQ_BAL_KEEP标志失效的风险,因此SCX_DSP_PREV → SCX_DSP_LOCAL的降级检查已过时。 - 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_BALANCE 是 sched_class->balance() 时代的产物。现在 sched_class->balance() 已经被移除,函数实际就是 dispatch;名字和职责已经错位。
新流程
- 0001:
put_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。 - 0003:
balance_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: 4c95380701f5,Cc: 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 命名。