sched-ext discussion
[PATCH sched_ext/for-7.3] sched_ext: Set up ops.sub_ecaps_updated() dispatch context on the executing CPU
LLM 分析
sched_ext:sub_ecaps_updated 调度上下文错位修复
系列概况
| 字段 | 内容 |
|---|---|
| 标题 | [PATCH sched_ext/for-7.3] sched_ext: Set up ops.sub_ecaps_updated() dispatch context on the executing CPU |
| 作者 | Tejun Heo tj@kernel.org |
| 版本 | 单封补丁,目标分支 sched_ext/for-7.3(for-7.3 表示将在 7.3 合入周期合入) |
| 规模 | 1 个文件,+5 / -1 |
| 修改文件 | kernel/sched/ext/sub.c |
| 代码统计 | kernel/sched/ext/sub.c | 6 +++++- |
| Message-ID | 874500f5212b5d192092950ea8d284b8@kernel.org |
| 完整性 | 主体完整;第 3 封 Andrea Righi 的回复在 lore 上显示被截断,仅引用了前两段背景,没有可译正文 |
补丁目的
修复 scx_process_sync_ecaps() 在 core scheduling(core 调度)下,处理 sibling rq 的有效能力(effective caps)同步时,使用了错误的 dsp_ctx。原代码从 llist 节点取出同步目标 CPU 的 pcpu->dsp_ctx,但调度 kfunc 实际通过 this_cpu_ptr() 解析正在执行的 CPU 上的上下文,导致 sibling rq 的同步触发 sub_ecaps_updated() 时,所使用的 dsp_ctx 来自执行 CPU,但 rq 字段却指向 sibling,破坏调用约定、出现 NULL 或陈旧 rq。
旧流程的问题
scx_process_sync_ecaps() 在被 sibling rq 触发时:
- 从 llist 节点拿到目标 CPU 的
pcpu; - 取
&pcpu->dsp_ctx作为当前 dispatch 上下文; - 把
dspc->rq = rq写入该上下文; - 调用
scx_bpf_sub_ecaps_updated()。
但调度 kfunc 内部使用的是 this_cpu_ptr(),实际拿到的是 执行 CPU 的 dsp_ctx,而不是上面写入的 pcpu->dsp_ctx。在 core scheduling 下 balance_one() 可能让 CPU A 处理属于 CPU B 的 sync 批次:
- 执行 CPU 的
dsp_ctx没有被scx_process_sync_ecaps()初始化; rq指针要么为 NULL,要么是别的调用残留的陈旧值;- dispatch buffer 也错位,下游 enqueue/dispatch kfunc 会把任务派发到错误的 rq,甚至在未持锁的 rq 上操作。
新流程
改为使用 执行 CPU 的 dsp_ctx,匹配 scx_dispatch_sched() 的写法:
struct scx_dsp_ctx *dspc = &this_cpu_ptr(pcpu->sch->pcpu)->dsp_ctx;
dspc->rq = rq;
dspc->rq 仍指向 sync 的目标 rq,所以 dispatch 的目标 rq 不变;dsp_ctx 自身落在执行 CPU 上,和 dispatch kfunc 的 this_cpu_ptr() 解析路径一致,避免错位。
Patch 概览
--- a/kernel/sched/ext/sub.c
+++ b/kernel/sched/ext/sub.c
@@ -1017,11 +1017,15 @@ void scx_process_sync_ecaps(struct rq *r
* invocation is equivalent to the dispatch path and may drop
* and re-acquire the rq lock temporarily while the rest of
* @batch is held privately, see scx_discard_ecaps_to_sync().
+ * The dispatch kfuncs resolve their context on the executing
+ * cpu, which under core scheduling can differ from @rq's cpu,
+ * so the context is set up there. The rq recorded in it keeps
+ * the dispatches targeting @rq.
*/
if (ecaps != pcpu->reported_ecaps && SCX_HAS_OP(pcpu->sch, sub_ecaps_updated)
&& !scx_bypassing(pcpu->sch, cpu)) {
- struct scx_dsp_ctx *dspc = &pcpu->dsp_ctx;
+ struct scx_dsp_ctx *dspc = &this_cpu_ptr(pcpu->sch->pcpu)->dsp_ctx;
dspc->rq = rq;
Fixes: 指向 b81a6c018cde(引入 sub_ecaps_updated() 的提交),Reported-by: 是 David Carlier。
关键实现
- 执行 CPU vs 目标 rq 的解耦:
dsp_ctx必须落在执行 CPU,rq字段仍指向目标 rq。这是 scx 的通用约定——this_cpu_ptr()拿到执行者本地的 buffer 和状态,而dspc->rq决定 dispatch 落到哪个 rq。 - core scheduling 路径:
balance_one()会让同一执行 CPU 处理兄弟 rq 的 sync 批次;如果不修,旧逻辑会把兄弟 rq 的pcpu->dsp_ctx当成本 CPU 的来用,触发上下文污染。 - 与
scx_dispatch_sched()对齐:本次改动刻意写成与 dispatch 主路径相同的形式,便于后续维护者识别“dispatch context 永远在执行 CPU”这条不变量。
类比
可以把 dsp_ctx 想象成 快递员的工装口袋:
- 工装口袋是 穿在身上的(执行 CPU),里面的笔、票据、托运单都长在口袋里。
- 托运单上写的 目的地 是
rq(目标 CPU 的运行队列)。 - 旧代码把目的地是 B 的快递,硬塞进了 CPU A 的快递员的工装——笔和票据都是 A 的,但目的地写 B,于是扫码系统要么报错,要么把 A 的票据错寄到 B。
- 修法是明确:必须用 A 快递员自己的工装口袋来装,但口袋里那张写着 B 地址的托运单保留不变。这样所有工具都对得上,目的地也仍然正确。
Highlight:风险与注意点
- 同类 bug 可能潜伏:scx 内部凡是直接用
&per_cpu_var->xxx而不是this_cpu_ptr(&per_cpu_var)->xxx的 dispatch 上下文初始化路径,都要重新审视;本次只在sub_ecaps_updated()路径上修,其他 dispatch 入口若同样在 core scheduling 下被 sibling rq 触发,可能复现同类错位。 rq与cpu不一致时的锁语义:dspc->rq = rq之后,dispatch kfunc 可能在该 rq 上持锁操作;调用方需要保证rq->lock的获取顺序与balance_one()的 sibling 处理一致,否则存在死锁风险。- 回归验证重点:需要在开启
SCHED_CORE/ core scheduling 的拓扑上做 sched_ext 的 stress 测试,覆盖 sibling rq 边界处的 effective caps 变化(例如 CPU 频率剧烈切换、cpufreq 过渡),观察是否还有 NULLrq或 dispatch buffer 越界。 - 后续观察点:第 3 封 Andrea Righi 的回复被 lore 截断,无法确认是否还有 ack / review 建议;建议直接到 lore 链接查看完整正文。
版本变化
本系列只有一封补丁,无 vN→vN+1 演进;目标分支 sched_ext/for-7.3,Tejun Heo 在第 2 封邮件中宣告 Applied。
一句话总结
把 scx_process_sync_ecaps() 的 dsp_ctx 从“目标 CPU 的 pcpu”改为“执行 CPU 的 pcpu”,避免 core scheduling 下 sibling rq 处理时 dispatch kfunc 拿到错位的 rq 和 dispatch buffer。