sched-ext discussion
[PATCH sched_ext/for-7.3-fixes] sched_ext: Fix spurious aborts in scx_bpf_dsq_move() on ownership change races
LLM 分析
sched_ext:修复 scx_bpf_dsq_move() 中所有权变更竞态导致的误终止
系列概况
- 标题:[PATCH sched_ext/for-7.3-fixes] sched_ext: Fix spurious aborts in scx_bpf_dsq_move() on ownership change races
- 作者:Tejun Heo tj@kernel.org
- 版本:单版本(for-7.3-fixes 分支,1/1)
- 规模:1 个文件,13 行新增 / 8 行删除
- 修改文件:
kernel/sched/ext/ext.c - 代码统计:+13 / -8
- Message-ID:
7b3b7e35d462fe061105f09de3a2eba9@kernel.org - 完整性:原 patch + maintainer applied 回复,完整闭环
补丁目的
scx_bpf_dsq_move() 在遍历 DSQ 时会对任务做"是否属于当前 scheduler"的所有权检查,若不匹配则调用 scx_error() 中止整个 BPF scheduler。该检查最初是为了防止错误操作他人调度器上的任务。
但任务所有权可能在任意时刻发生变化:任务可能运行后退出并清除关联,也可能被重新归属到另一个 sub-sched。这两种都是良性的竞态,但旧版代码会在竞态窗口内把良性的所有权漂移误判为严重违规,导致整个 BPF scheduler 被无故 abort。
补丁目标:消除这类误终止,保留对真正所有权违规的检测能力。
旧流程的问题
scx_dsq_move() 在函数开头、未加锁时就检查 scx_task_on_sched(sch, p),并把任务已经离开当前 scheduler 的竞态情况直接报告为错误。
caller -> scx_dsq_move()
|
check sch->aborting (fail if true)
|
check scx_task_on_sched(sch, p) <-- too early
|
acquire src_dsq->lock
|
check p still on src_dsq (cursor lost?)
问题在于:即便任务确实已经离开当前 scheduler(良性 race),旧代码也照样 abort。这相当于把"任务暂时换了主人"当成"调度器越权操作",把可恢复的竞态升级成了致命的 scheduler 终止。
新流程
把所有权检查推迟到拿锁之后、"任务仍在源 DSQ" 校验通过之后执行:
caller -> scx_dsq_move()
|
check sch->aborting (fail if true)
|
acquire src_dsq->lock
|
if p not on src_dsq <-- cursor-lost, unlock + return false
|
if !scx_task_on_sched(sch, p) <-- now it is real violation
scx_error() + abort unlock + return
关键洞察:sched_ext 保证"任何所有权变更前都会先从源 DSQ 摘除任务"。因此当我们在 src_dsq->lock 保护下仍然看到 @p 在该 DSQ 上时,调用方一定是先前把它放进去的那一方;若此刻 @p 不属于 @sch,那就是真正的越权。否则(cursor 已经丢失),任务已经离开 DSQ,所有权变化是良性的,直接返回即可。
Patch 概览
仅一个 patch,修改 kernel/sched/ext/ext.c:
- 删除函数开头的所有权检查块(移走而非删掉)。
- 在
raw_spin_unlock(&src_dsq->lock); goto out;这一 cursor-lost 早退分支之后,重新插入同一段所有权检查;abort之前先 unlock。 - 顺手把
scx_root_enable_workfn()中两处仍引用旧名sched_ext_free()的注释改为sched_ext_dead()。
关键实现
static bool scx_dsq_move(struct bpf_iter... )
{
if (unlikely(READ_ONCE(sch->aborting)))
return false;
/* acquire lock and verify cursor */
raw_spin_lock(&src_dsq->lock);
...
if (!task_on_dsq(p)) {
raw_spin_unlock(&src_dsq->lock);
goto out;
}
/*
* @p has been on $src_dsq and can't move anymore.
* If @p is not on @sch, the caller didn't have authority
* over @p at the time of the call.
*/
if (unlikely(!scx_task_on_sched(sch, p))) {
scx_error(sch,
"scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler",
p->comm, p->pid);
raw_spin_unlock(&src_dsq->lock);
goto out;
}
/* @p is still on $src_dsq and stable, determine the destination */
dst_dsq = find_dsq_for_dispatch(...);
...
}
注释方面,把 scx_root_enable_workfn() 内两处 sched_ext_free() 改成 sched_ext_dead(),保持与当前代码命名一致。
类比
把这套机制想象成小区门禁:业主名单(所有权)会动态更新(搬家、退租),但每户业主一旦离开都会先归还门禁卡(先从 DSQ 出队)。
旧版保安在门口就比对名单(任务是否属于本小区),只要名单晚到一秒就拉响警报,把"住户临时外出"误判成"外人闯入"。
新版则要求:先确认此人还在小区内(lock + 仍在 DSQ),再比对名单——若人在小区却查不到业主身份,才是真入侵;若人已经出门了,只是名单刷新慢一点,安安静静关门即可。
ASCII 流程图
+----------------------------------------------------------+
| Old path: ownership check too early -> spurious abort |
+----------------------------------------------------------+
scx_dsq_move(p)
|
+-- sch->aborting ? ---------- yes --> return false
| no
+-- scx_task_on_sched(sch,p) ? - no --> scx_error + ABORT (false alarm)
| yes
+-- lock(src_dsq) --> cursor lost ? -> return false
+-- dispatch ...
+----------------------------------------------------------+
| New path: lock first, then verify ownership |
+----------------------------------------------------------+
scx_dsq_move(p)
|
+-- sch->aborting ? ---------- yes --> return false
| no
+-- lock(src_dsq) --> cursor lost ? -> return false (benign exit)
| no
+-- scx_task_on_sched(sch,p) ? - no --> scx_error + ABORT (real violation)
| yes
+-- dispatch ...
+----------------------------------------------------------+
| Ownership-change timing invariant |
+----------------------------------------------------------+
ownership change = dequeue first --> then re-attach to new sch
^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
visible under lock only visible lock-less
Highlight:风险与注意点
- 判断语义变了:原来 abort 条件是"调用时任务不在 sch 上";新版是"在 lock 下任务仍在 src_dsq,但又不属于 sch"。二者在 race 窗口里结果截然不同,BPF scheduler 作者需要重新理解这条错误信息的触发条件。
- 错误信息仍保留原文:补丁只是把检查挪位置,错误文本
scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler不变,便于日志/告警向后兼容。 - 注释修复是顺手活:把
sched_ext_free()改成sched_ext_dead()表明这是一个长期遗留问题,未来在文档或注释里若再次出现旧名字,应一并清理,避免误导。 - 回归测试:建议社区在 scx 测试套件里加 race 复现——例如任务在
dispatch迭代期间被 rehome 到另一个 sub-sched,验证新版不会 abort。 - 潜在 follow-up:
scx_dsq_move_vtime()与该函数共享同一路径(错误信息里两个名字并列),需要确认其调用路径是否有同样的提前检查;若存在需一并后移。
版本变化
本系列只有 v1(for-7.3-fixes 分支),随后被 maintainer 直接打上"Applied to sched_ext/for-7.3-fixes",无后续迭代。
一句话总结
把 scx_dsq_move() 中过早的"任务所有权"检查推迟到加锁且确认任务仍在源 DSQ 之后,使良性所有权变更不再触发 scheduler abort,同时顺手修掉两处仍引用旧名 sched_ext_free() 的陈旧注释。