sched-ext discussion
[PATCH v2 sched_ext/for-7.2-fixes] sched_ext: Preserve rq tracking across local DSQ dispatch
LLM 分析
系列基线信息
- 标题:
[PATCH v2 sched_ext/for-7.2-fixes] sched_ext: Preserve rq tracking across local DSQ dispatch - 作者: Andrea Righi
<arighi@nvidia.com> - 版本: v2
- 规模: 单补丁,修改
kernel/sched/ext/ext.c,约 28 行新增、1 行删除 - Message-ID:
20260707173851.153379-1-arighi@nvidia.com - 来源频道:
sched-ext - 目标分支/语境:
sched_ext/for-7.2-fixes - 修复对象:
sched_ext中scx_locked_rq()的 rq 跟踪状态在切换 runqueue 锁时可能与真实持锁状态不一致 - 相关 Fixes:
7fb39e4eb4c3 ("sched_ext: Save and restore scx_locked_rq across SCX_CALL_OP") - 稳定版标记:
Cc: stable@vger.kernel.org # 7.1+
明确目的
这个讨论围绕一个 sched_ext bugfix:在 ops.dispatch() 执行期间,系统会记录“当前持有锁的 rq”,也就是 scx_locked_rq()。但某些路径会在嵌套调用 BPF scheduler 回调之前切换 runqueue 锁,导致记录的 rq 仍然指向旧 rq,而旧 rq 实际上已经不再持锁。
问题触发路径大致是:
ops.dispatch()
→ scx_bpf_dsq_move_to_local()
→ dispatch_to_local_dsq()
→ scx_dispatch_enqueue()
→ call_task_dequeue()
→ 嵌套调用 ops.dequeue()
嵌套回调会保存并恢复当前记录的 rq。如果记录的 rq 已经过期,恢复时可能触发 lockdep 断言:
WARNING: kernel/sched/sched.h:1641 at call_task_dequeue+0x160/0x170
Andrea 的 v2 补丁目标是:在 dispatch_to_local_dsq() 和 scx_dsq_move() 中切换 rq 锁期间,临时清空 rq tracking,等重新拿回原始 rq 后再恢复,避免嵌套回调恢复一个并未真正持锁的 rq。
Tejun 的回复认可问题方向,但建议实现方式进一步改进:不要在函数边界保存/清空/恢复 tracking,而是把“释放一个 rq 锁、获取另一个 rq 锁”的动作封装成 helper,让 scx_locked_rq() 在整个锁切换过程中始终跟随真实持锁的 rq。
遍历代码
1. 原始问题路径
在 sched_ext 中,SCX_CALL_OP() 调用 BPF scheduler ops 时,会维护一个“当前持锁 rq”的记录。这个记录用于让嵌套回调知道自己处在什么 rq 锁上下文里。
出问题的场景是:
ops.dispatch()开始执行时,当前 rq 被记录为scx_locked_rq()。- BPF 调用
scx_bpf_dsq_move_to_local(),想把任务移动到本地 DSQ。 - 内核路径进入
dispatch_to_local_dsq()。 - 为了移动任务,代码可能释放当前 rq 锁,切换到源 rq 或目标 rq。
- 切换锁之后,
scx_locked_rq()仍然可能记录着旧 rq。 - 后续同步调用
ops.dequeue()。 - 嵌套回调保存旧 tracking,返回时恢复它。
- 但旧 rq 实际上已经不持锁,
update_locked_rq()恢复时触发 lockdep warning。
也就是说,问题不在于任务移动本身,而在于 “rq tracking 状态”和“真实 rq 锁状态”脱节。
2. Andrea v2 在 dispatch_to_local_dsq() 中的修复
v2 在 dispatch_to_local_dsq() 开头记录:
tracked_rq = scx_locked_rq()
如果当前存在 tracked rq,则:
- 检查
tracked_rq == rq - 调用
update_locked_rq(NULL)清空 tracking - 执行中间的 rq 锁切换和本地 DSQ dispatch
- 最后在重新获得原 rq 后调用
update_locked_rq(tracked_rq)恢复 tracking
核心思想是:
- 切换锁期间不要让嵌套回调看到一个可能已经不再持锁的 rq;
- 等锁状态回到原来的 rq 后,再恢复 tracking。
这像是在危险路段暂时摘掉“当前持锁 rq”标签,避免别人误用旧标签。
3. Andrea v2 在 scx_dsq_move() 中的修复
v2 相比 v1 的主要变化是:同样处理 scx_dsq_move()。
scx_dsq_move() 在 ops.dispatch() 活跃时也可能释放 this_rq 并切换到其他 rq。这个路径同样会导致 scx_locked_rq() 记录的 rq 和真实持锁 rq 不一致。
所以 v2 加入:
tracked_rq = scx_locked_rq()- 如果存在 tracked rq:
WARN_ON_ONCE(!in_balance)WARN_ON_ONCE(tracked_rq != this_rq)update_locked_rq(NULL)
- 完成锁切换和移动后:
update_locked_rq(tracked_rq)
这说明 v2 不只是修复一个具体函数,而是把同类 rq tracking 问题扩展到了另一个路径。
4. Tejun 的 review 建议
Tejun 没有否认 bug,而是指出 v2 的实现方式可以更自然。
Andrea 的方案是:
- 在函数边界保存 tracked rq;
- 函数中间清空;
- 函数末尾恢复。
Tejun 建议改为:
- 封装一个
switch_rq_lock(from, to)类型的 helper; - helper 内部判断
scx_locked_rq() == from; - 如果当前 tracking 指向将要释放的 rq,则先清空;
- 解锁
from; - 加锁
to; - 再把 tracking 更新为
to。
这样 scx_locked_rq() 会始终表示“当前真正持有的 rq”,而不是在整个函数执行期间变成 NULL。
Tejun 还建议把这个 helper 用到:
dispatch_to_local_dsq()move_remote_task_to_local_dsq()scx_dsq_move()中in_balance场景下的 unlock/lock 对
但 !in_balance 的 fresh lock 路径保持不变。
5. Tejun 对 consume path 的说明
Tejun 特别提到 consume path:由于 == from 判断,helper 对 consume path 会是 no-op,因为那里的 tracked rq 是 this_rq,不是正在释放的 rq。
他认为这在当前是无害的,因为 deactivate 运行在 migration guards 保护下;但最好在 for-7.3 里再单独把 consume path 也纳入同样 helper,使 rq tracking 在所有地方都更忠实地反映真实持锁状态。
6. Andrea 的回应
Andrea 接受 Tejun 的建议:
- 会很快发送 v3,把这些改动纳入
for-7.2-fixes; - 会后续再发一个单独的
for-7.3patch,让 consume path 的 rq tracking 也保持准确。
因此这条线程的最终状态是:v2 识别并覆盖了关键 bug,但 maintainer 建议重构成更局部、更一致的锁切换 helper,Andrea 同意并准备 v3。
ASCII 流程图
dispatch path before fix
ops.dispatch()
|
v
scx_bpf_dsq_move_to_local()
|
v
dispatch_to_local_dsq()
|
+--> unlock old rq / lock another rq
| |
| v
| scx_locked_rq still names old rq
|
v
call_task_dequeue()
|
v
restore stale rq tracking -> lockdep warning
Tejun suggested model
current rq lock + tracking
|
v
switch_rq_lock(from, to)
|
+--> if tracking == from: clear tracking
+--> unlock from
+--> lock to
+--> if tracked: tracking = to
|
v
tracking follows actually-held rq
概念类比
可以把 rq 锁想成仓库门禁卡,scx_locked_rq() 是白板上写着“当前我拿着哪间仓库钥匙”的记录。
Andrea v2 的方案像是:你要从 A 仓库换到 B 仓库时,先把白板擦空,避免别人以为你还拿着 A 仓库钥匙;等你回到 A 仓库时,再把 A 写回白板。这样可以避免别人根据错误白板记录去开门。
Tejun 的建议更像是:不要只在进出整栋楼时擦白板,而是在每一次换钥匙的瞬间更新白板。你从 A 换到 B,就立刻把白板从 A 改成 B。这样白板上始终写着你真正拿着的钥匙,而不是长时间空白,也不是写着旧钥匙。
这个区别很关键:Andrea 的修复避免了错误恢复旧 rq;Tejun 的 helper 则让 tracking 模型更精确、更容易维护,也减少了额外的 tracked_rq 局部变量和 WARN_ON_ONCE() 绑定条件。
Highlight 突出问题
- 不要误以为这是普通 DSQ enqueue/dequeue bug:核心问题是 rq lock tracking 与真实持锁状态不一致,DSQ 移动只是触发场景。
- 函数边界清空 tracking 虽然能修 bug,但粒度偏粗:Tejun 认为更好的抽象是围绕 unlock/lock pair 更新 tracking,让
scx_locked_rq()始终忠实描述当前状态。 in_balance条件与 tracking 之间存在耦合风险:Andrea v2 用WARN_ON_ONCE(!in_balance)等检查表达假设;Tejun 建议 helper 可以减少这种耦合。- consume path 暂时未完全统一:Tejun 说明当前无害,但希望
for-7.3后续补齐,让所有路径的 rq tracking 更一致。 - 这是 stable 相关修复:补丁带有
Cc: stable@vger.kernel.org # 7.1+,说明影响已发布或即将维护的版本,回归风险和修复准确性都比较重要。 - v3 很可能替代 v2:Andrea 已明确接受 review,会发送 v3,因此阅读这条线程时应把 v2 看作问题定位和初版修复,而不是最终合入形态。
版本演进
v1 → v2
v2 明确列出的变化是:
- 在
dispatch_to_local_dsq()之外,也把同样的 rq tracking 修复应用到scx_dsq_move()。 - 这来自 sashiko AI 的反馈。
- v2 仍采用“函数边界保存 tracked rq、切换期间清空、结束后恢复”的方式。
v2 → 预期 v3
根据 Tejun 的 review 和 Andrea 的回复,v3 预计会:
- 引入类似
switch_rq_lock(from, to)的 helper; - 在 helper 内部维护
scx_locked_rq(); - 在
dispatch_to_local_dsq()、move_remote_task_to_local_dsq()、scx_dsq_move()的相关 lock switch 处使用; - 去掉 v2 中的
tracked_rq局部 bookkeeping; - 去掉部分
WARN_ON_ONCE(); - 避免把
in_balance状态和scx_locked_rq()过度绑定。
后续 for-7.3
Andrea 表示会单独跟进 for-7.3 patch:
- 把 consume path 也纳入类似 helper;
- 让 rq tracking 在该路径上也保持准确。
与其他相关 patch 系列的关联
这条线程直接关联到此前的提交:
7fb39e4eb4c3 ("sched_ext: Save and restore scx_locked_rq across SCX_CALL_OP")
该提交引入或调整了跨 SCX_CALL_OP 保存/恢复 scx_locked_rq() 的行为。本补丁是在这个机制之上修复一个边界情况:当回调内部发生 rq 锁切换时,保存/恢复机制可能恢复一个已经不再持锁的 rq。
线程中还提到:
- v1 链接:
https://lore.kernel.org/all/20260707135854.1379730-1-arighi@nvidia.com/ - 后续预期:一个
for-7.3的 consume path 跟进补丁
一句话总结
这是一个 sched_ext rq lock tracking 的重要 bugfix 讨论:v2 通过清空/恢复 tracking 避免 lockdep warning,maintainer 建议 v3 改成围绕 rq 锁切换的 helper,让 scx_locked_rq() 始终跟随真实持锁 rq。