0/4 已展开

LLM 分析

sched_ext:修复 consume 路径的 rq 锁追踪

系列概况

  • 标题:[PATCH sched_ext/for-7.3] sched_ext: Keep rq tracking accurate in the consume path
  • 作者:Andrea Righi arighi@nvidia.com
  • 版本:无 vN;面向 sched_ext/for-7.3 的单 patch
  • 规模:1 个实际 patch
  • 修改文件:1 个(kernel/sched/ext/ext.c
  • 代码统计:+13/-13
  • Message-ID:20260709051708.306636-1-arighi@nvidia.com
  • 完整性:完整;1/1 patch 加 3 封回复,无缺失或 tip-bot2 污染;投稿 DKIM 为 BADSIG

一句话总结:把远程 DSQ 消费过程中的 rq 解锁/加锁改成可追踪的直接切换,使 scx_locked_rq() 始终对应实际持有的 rq。

补丁目的

sched_ext 在执行 BPF ops.dispatch() 时,用 per-CPU 的 scx_locked_rq_state 记录当前已经持有哪个 runqueue(rq)锁,scx_locked_rq() 返回该记录。kfunc 和嵌套回调据此判断能否安全操作 rq。

consume_remote_task() 要把另一 CPU 的任务从共享 DSQ 搬到本 CPU local DSQ,需要把锁从 this_rq 换成任务所在的 src_rq,完成迁移后再换回来。旧代码切换了物理锁,却没有同步追踪状态,导致持有 src_rq 的窗口内追踪仍指向 this_rq

旧流程的问题

旧路径先直接释放 this_rq,但 scx_locked_rq_state 仍是 this_rq

raw_spin_rq_unlock(this_rq);
raw_spin_rq_lock(src_rq);

随后成功路径虽然调用 switch_rq_lock(src_rq, this_rq),该 helper 只在 scx_locked_rq() == from 时更新追踪。此时记录是 this_rqfrom 却是 src_rq,guard 不匹配,所以它只切物理锁,不更新记录。

返回时物理锁和记录都重新是 this_rq,并非永久错误;真正的问题是持有 src_rq 并执行 deactivate_task() 等工作的中间窗口,回调或 kfunc 可能依据错误 rq 做判断。

新流程

ops.dispatch():物理锁=this_rq,tracking=this_rq
                       |
                 锁住 DSQ、选中远程任务
                       |
           从 DSQ 摘除任务,设置 holding_cpu
                       |
                   解锁 DSQ
                       |
          switch_rq_lock(this_rq -> src_rq)
          物理锁=src_rq,tracking=src_rq
                  /                 
           dequeue 竞争失败          任务仍有效
                  |                   |
   switch_rq_lock(src -> this)   deactivate/set_task_cpu
                  |              switch_rq_lock(src -> this)
                  +---------+---------+
                            |
             物理锁=this_rq,tracking=this_rq

旧:先用 raw spinlock 操作制造“不持有 rq 锁”和“持有 src 但记录 this”的窗口。

新:保持 this_rq 到 DSQ 解锁,然后通过 switch_rq_lock() 直接转到 src_rq;成功与失败路径都用同一机制切回。

关键实现

1. helper 协议改变

unlink_dsq_and_lock_src_rq() 更名为 unlink_dsq_and_switch_rq_lock(),增加 locked_rq 参数和:

lockdep_assert_rq_held(locked_rq);

调用约束从“只持有 DSQ 锁”变成“同时持有 DSQ 与 locked_rq”。函数摘除任务并释放 DSQ 锁后,调用 switch_rq_lock(locked_rq, src_rq)

2. 追踪更新的 guard

前置 sched_ext/for-7.2-fixes 提供的 helper 核心语义是:

tracked = scx_locked_rq() == from;
if (tracked) update_locked_rq(NULL);
unlock(from); lock(to);
if (tracked) update_locked_rq(to);

补丁的关键不是简单换一种加锁 API,而是保证每次传入的 from 与当前 tracking 一致,让 guard 能连续更新状态。

3. dequeue 竞争仍由 holding_cpu 仲裁

从 DSQ 摘除任务后设置 p->scx.holding_cpu,再释放 DSQ 并切 rq 锁。若并发 dequeue 抢先处理任务,会清除该字段;获得 src_rq 后重新检查即可判断是否输掉竞争。helper 无论返回 true 或 false,都已经持有 src_rq,不存在“early return 时没锁 src_rq”的分支。

4. 成功路径确实恢复 tracking

最终基线里的 move_remote_task_to_local_dsq() 已使用:

switch_rq_lock(src_rq, dst_rq);

因此成功路径从 src_rq 切回作为目标的 this_rq 时会同步 tracking。Sashiko 看到 raw unlock/lock,是因为它分析的 for-7.3 未合并补丁声明的 for-7.2-fixes 前置分支。

类比

把 rq 锁看成机房钥匙,scx_locked_rq_state 是值班系统里的“当前所在机房”。旧流程用普通钥匙离开 A、进入 B,系统仍登记 A;虽然最后回到 A 时账面碰巧恢复正确,但在 B 工作期间,安全检查一直依据错误位置。新流程统一走正式换钥匙手续:先注销 A、取得 B 后登记 B,返回时再登记 A,实体钥匙与电子记录始终一致。

Highlight:风险与注意点

  • 基线依赖:投稿明确要求将 sched_ext/for-7.2-fixescfe950d79f524 合入 for-7.3;脱离该基线单独应用会缺少 switch_rq_lock(),也会让成功路径仍使用 raw spinlock。
  • Sashiko review:其“未定义 helper”和“成功路径永久 stale”两项告警均基于错误基线。Andrea 回复“两项都是 false positive”,最终代码也验证了这一点。
  • 锁时长变化:新路径比旧路径更久地持有 this_rq,直到任务从 DSQ 摘除并释放 DSQ 锁;操作很短,但这是修复追踪连续性的直接代价。
  • 锁顺序:切换前先释放 DSQ,switch_rq_lock() 再释放旧 rq、获取新 rq;不会同时持有两个 rq 锁,holding_cpu 负责覆盖无 rq 锁的竞争窗口。
  • 验证范围:patch 只增加 lockdep 断言,没有新增自测;CONFIG_LOCKDEP 可发现调用约束错误,生产配置仍依赖协议本身正确。
  • 合入状态:Tejun Heo 回复已合入 sched_ext/for-7.3;linux-next 最终提交为 3d1519011e395ea96c7fbc5ced35b49cfe60d93e,代码与投稿一致,仅增加 Tejun 的 Signed-off-by。

版本变化

线程没有 v2。Sashiko 报告两项问题后,作者解释它使用了缺少 for-7.2-fixes 合并的基线;维护者未要求改代码,次日直接确认应用。最终提交保持 +13/-13 的原始 diff。

一句话总结

该补丁把远程任务消费中的 rq 锁“物理切换”和 scx_locked_rq() 的“账面切换”合并为同一套连续操作,消除持有 src_rq 时 tracking 仍指向 this_rq 的并发语义漏洞。