sched-ext discussion
[PATCH sched_ext/for-7.3] sched_ext: Keep rq tracking accurate in the consume path
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_rq、from 却是 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-fixes的cfe950d79f524合入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 的并发语义漏洞。