0/3 已展开

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-ID874500f5212b5d192092950ea8d284b8@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 触发时:

  1. 从 llist 节点拿到目标 CPU 的 pcpu
  2. &pcpu->dsp_ctx 作为当前 dispatch 上下文;
  3. dspc->rq = rq 写入该上下文;
  4. 调用 scx_bpf_sub_ecaps_updated()

但调度 kfunc 内部使用的是 this_cpu_ptr(),实际拿到的是 执行 CPUdsp_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 上操作。

新流程

改为使用 执行 CPUdsp_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。

关键实现

  1. 执行 CPU vs 目标 rq 的解耦dsp_ctx 必须落在执行 CPU,rq 字段仍指向目标 rq。这是 scx 的通用约定——this_cpu_ptr() 拿到执行者本地的 buffer 和状态,而 dspc->rq 决定 dispatch 落到哪个 rq。
  2. core scheduling 路径balance_one() 会让同一执行 CPU 处理兄弟 rq 的 sync 批次;如果不修,旧逻辑会把兄弟 rq 的 pcpu->dsp_ctx 当成本 CPU 的来用,触发上下文污染。
  3. scx_dispatch_sched() 对齐:本次改动刻意写成与 dispatch 主路径相同的形式,便于后续维护者识别“dispatch context 永远在执行 CPU”这条不变量。

类比

可以把 dsp_ctx 想象成 快递员的工装口袋

  • 工装口袋是 穿在身上的(执行 CPU),里面的笔、票据、托运单都长在口袋里。
  • 托运单上写的 目的地rq(目标 CPU 的运行队列)。
  • 旧代码把目的地是 B 的快递,硬塞进了 CPU A 的快递员的工装——笔和票据都是 A 的,但目的地写 B,于是扫码系统要么报错,要么把 A 的票据错寄到 B。
  • 修法是明确:必须用 A 快递员自己的工装口袋来装,但口袋里那张写着 B 地址的托运单保留不变。这样所有工具都对得上,目的地也仍然正确。

Highlight:风险与注意点

  1. 同类 bug 可能潜伏:scx 内部凡是直接用 &per_cpu_var->xxx 而不是 this_cpu_ptr(&per_cpu_var)->xxx 的 dispatch 上下文初始化路径,都要重新审视;本次只在 sub_ecaps_updated() 路径上修,其他 dispatch 入口若同样在 core scheduling 下被 sibling rq 触发,可能复现同类错位。
  2. rqcpu 不一致时的锁语义dspc->rq = rq 之后,dispatch kfunc 可能在该 rq 上持锁操作;调用方需要保证 rq->lock 的获取顺序与 balance_one() 的 sibling 处理一致,否则存在死锁风险。
  3. 回归验证重点:需要在开启 SCHED_CORE / core scheduling 的拓扑上做 sched_ext 的 stress 测试,覆盖 sibling rq 边界处的 effective caps 变化(例如 CPU 频率剧烈切换、cpufreq 过渡),观察是否还有 NULL rq 或 dispatch buffer 越界。
  4. 后续观察点:第 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。