sched-ext discussion
[PATCH sched_ext/for-7.2-fixes] sched_ext: Preserve rq tracking across local DSQ dispatch
LLM 分析
sched_ext:修复 local DSQ 切锁期间的陈旧 rq 跟踪
系列概况
- 标题:
[PATCH sched_ext/for-7.2-fixes] sched_ext: Preserve rq tracking across local DSQ dispatch - 作者:Andrea Righi
<arighi@nvidia.com> - 版本:未标版本(单 patch)
- 规模:1 个实际 patch
- 修改文件:1 个,
kernel/sched/ext/ext.c - 代码统计:
+13/-0 - Message-ID:
20260707135854.1379730-1-arighi@nvidia.com - 完整性:本地页面与原始邮件标题一致;b4 得到 2 封邮件、1/1 patch,无
Thread incomplete、缺件或 tip-bot2 污染;但 b4 报BADSIG: DKIM/nvidia.com,邮件签名真实性未获工具确认
一句话总结:在
dispatch_to_local_dsq()临时切换 rq 锁时暂停scx_locked_rq跟踪,避免嵌套回调恢复一个实际已解锁的 rq 并触发 lockdep。
补丁目的
SCX_CALL_OP() 会把当前持锁 rq 记入每 CPU 的 scx_locked_rq_state,让 BPF kfunc 和嵌套 SCX 回调知道当前受哪个 rq 锁保护。此前为支持嵌套 sub-scheduler,提交 7fb39e4eb4c3 又让内层回调在退出时恢复外层记录。
问题在于,外层 ops.dispatch() 可调用 scx_bpf_dsq_move_to_local(),其后 dispatch_to_local_dsq() 为跨 rq 移动任务会放下原 rq 锁、改持源或目标 rq 锁。真实锁已经变化,外层记录却仍指向原 rq;同步触发的 ops.dequeue() 因而可能在返回时恢复陈旧记录,并被 update_locked_rq() 内的 lockdep_assert_rq_held() 捕获。
补丁只修正锁上下文记账,不改变任务选择、DSQ 顺序、BPF ABI 或调度策略;Cc: stable@vger.kernel.org # 7.1+ 表明作者希望回补受影响的 7.1 及以后版本。
旧流程的问题
旧路径的关键矛盾是“记录的锁”和“实际持有的锁”短暂分离:
SCX_CALL_OP(dispatch, rq)记录原始rq。dispatch_to_local_dsq()解锁该 rq,转而锁住src_rq/dst_rq。scx_dispatch_enqueue()经local_dsq_post_enq()同步调用call_task_dequeue()。- 内层
SCX_CALL_OP_TASK(dequeue, locked_rq, ...)保存陈旧的外层记录。 - 内层退出并恢复该记录时,原 rq 尚未重新加锁,lockdep 报警。
邮件给出的现场是 kernel/sched/sched.h 中的断言告警,栈从 call_task_dequeue() 回溯至 dispatch_to_local_dsq()、scx_flush_dispatch_buf() 和 BPF ops.dispatch()。
新流程
SCX_CALL_OP(dispatch, rq)
│ scx_locked_rq_state = rq
▼
dispatch_to_local_dsq()
├─ 无需切锁的两个早退路径 ───────────────► 直接返回(记录不变)
│
├─ tracked_rq = scx_locked_rq()
├─ WARN_ON_ONCE(tracked_rq != rq)
├─ update_locked_rq(NULL) 暂停外层 rq 记账
│
├─ rq → src_rq/dst_rq 锁切换
├─ nested ops.dequeue()
│ └─ 保存/恢复的是 NULL 或其真实 locked_rq,不再恢复旧 rq
│
├─ 重新获取原始 rq 锁
└─ update_locked_rq(tracked_rq) 恢复外层上下文
旧:rq 指针的“账面状态”跨越了实际解锁窗口。
新:切锁窗口把账面状态置空,只在重新持有原 rq 后恢复。
Patch 概览
| Patch | 核心改动 |
|---|---|
| 1/1 | 在 dispatch_to_local_dsq() 保存 tracked_rq,切锁前清空跟踪,重新锁回原 rq 后恢复 |
关键实现
1. 快速路径保持原样
tracked_rq 虽在函数入口读取,但清空动作位于两个早退检查之后:任务已在目标本地 rq,或目标 CPU 不可运行而回退 global DSQ 时,都没有 rq lock dancing,因此无需改动跟踪状态。
2. 清空的是元数据,不是锁
核心新增逻辑等价于:
if (tracked_rq) {
WARN_ON_ONCE(tracked_rq != rq);
update_locked_rq(NULL);
}
update_locked_rq(NULL) 只把每 CPU 的 scx_locked_rq_state 置空,并不执行解锁;真正的 raw_spin_rq_unlock()/raw_spin_rq_lock() 顺序仍由原代码控制。这样嵌套回调不会继承已经过期的外层锁声明。
3. 恢复时机受真实锁约束
函数尾部原逻辑先把 locked_rq 切回参数 rq,补丁随后才调用 update_locked_rq(tracked_rq)。由于非空更新会执行 lockdep_assert_rq_held(rq),顺序反过来仍会复现告警。
4. 并发协议没有被绕开
任务仍先以 SCX_OPSS_DISPATCHING 获得独占所有权,再设置 holding_cpu,以 release 语义清除 ops state,然后按既有协议锁定 src_rq、移动任务并返回原 rq。补丁没有改变 ops_state、holding_cpu 或 rq 锁序,只校正嵌套 callback 可见的锁上下文。
当前工作树 v7.2-rc2-22-g0e35b9b6ec0f 尚未包含该提交。原始邮件基于 blob b6a635ba269cc,当前文件是 691d53fe0f648,但上下文仍通过 git apply --check,说明投稿与当前树有基线差异而非补丁缺失。
类比
把 rq 锁看成机房钥匙,把 scx_locked_rq_state 看成门口的值班牌。原实现临时归还 A 机房钥匙、改拿 B 的钥匙时,牌子仍写“持有 A”;嵌套回调照牌子办事便被门禁发现不符。补丁在换钥匙期间先把牌子擦空,真正重新拿回 A 的钥匙后再写回。
Highlight:风险与注意点
WARN_ON_ONCE(tracked_rq != rq)是不变量检查而非恢复策略;若它触发,末尾恢复tracked_rq仍可能再次撞上 lockdep,意味着调用方锁上下文本身需要修复。- 清空后到函数尾部没有新增早退;未来若在此区间加入
return或错误路径,必须先恢复原 rq 锁及跟踪,否则会泄漏 NULL 状态。 - sashiko-bot 指出
scx_dsq_move()在in_balance且跨 remote rq 时也会放下this_rq,却未暂停同一跟踪,可能存在同类陈旧状态。当前源码可见该切锁窗口,但这只是 AI review 提出的既有风险,本 patch 未处理,也没有人工维护者结论。 - 每次受影响调用增加一次每 CPU 读取和至多两次写入;相对跨 rq 锁切换与任务移动,预计开销很小,且不改变用户可见接口。
- 线程只有 sashiko-bot 回复:无
Reviewed-by/Acked-by,无人工回复不能表述为已获认可;也未发现 v2/v3 或最终合入提交。 - b4 的 NVIDIA DKIM 校验为 BADSIG,影响邮件来源认证判断,不改变 diff 完整性校验结果。
一句话总结
该补丁以“切锁前清空、锁回后恢复”的最小改动,使 scx_locked_rq_state 始终与真实 rq 锁同步,修复 local DSQ 派发中的嵌套回调 lockdep 告警;但 scx_dsq_move() 的相似窗口仍待人工确认和后续处理。