sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Sub-scheduler and cid fixes
LLM 分析
sched_ext: Sub-scheduler and cid fixes — 技术分析
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | sched_ext: Sub-scheduler and cid fixes |
| 作者 | Tejun Heo (tj@kernel.org) |
| 目标分支 | sched_ext/for-7.3 (b20dfde5ec54) |
| 补丁数 | 3 |
| 涉及文件 | ext.c, cid.c, cid.h |
| 变更统计 | +42 / −12 |
| Message-ID | 20260720082605.1451945-1-tj@kernel.org |
| 来源 | sched-ext |
明确目的
本系列修复 sched_ext 子调度器层级与拓扑 CPU ID (cid) 机制中的三个 bug:
- Stall 归责错位:bypass 子调度器的任务停在祖先 bypass DSQ 上,watchdog 却归责给任务所属的子调度器,而真正该排空该 DSQ 的祖先逃过了追责。
- Bypass 下无效 CPU 选择:bypass 时
select_task_rq_scx()仍走默认 CPU 选择路径,但结果被 bypass enqueue 直接丢弃;若调度器自行管理 idle mask,内核 idle cpumask 已冻结,选择结果更是错的。 - CID 表 NULL 解引用:首次 enable 时 cid 表在
ops->priv发布后才分配,TRACING/SYSCALL BPF 程序可在表尚未分配的窗口调用 cid kfunc,触发 NULL 指针解引用。
遍历代码
Patch 1 — Stall 归责修正 (ext.c)
check_rq_for_timeouts() 原逻辑:发现 runnable stall 后直接归责任务所属 scheduler (sch = task_owner)。
补丁在超时检测前插入一段判断:
struct scx_dispatch_q *dsq = READ_ONCE(p->scx.dsq);
if (dsq && dsq->sched && dsq->id != SCX_DSQ_LOCAL)
sch = dsq->sched;
- 读任务的当前 DSQ;
- 若 DSQ 有
sched(即被某 scheduler 拥有)且非SCX_DSQ_LOCAL,则将sch替换为 DSQ 的 owning scheduler; SCX_DSQ_LOCAL由 CPU 自身消费,归责仍留在任务 owner。
Patch 2 — Bypass 下跳过默认 CPU 选择 (ext.c)
select_task_rq_scx() 原逻辑:bypass 时仍走 scx_select_cpu_dfl() → direct dispatch。
补丁在函数入口处加 early return:
if (bypassing) {
__scx_add_event(sch, SCX_EV_BYPASS_DISPATCH, 1);
p->scx.selected_cpu = prev_cpu;
return prev_cpu;
}
- bypass 时直接返回
prev_cpu; - 不再调用
scx_select_cpu_dfl(),避免读冻结的 idle mask; - bypass enqueue 路径会自行将任务放入 bypass DSQ,CPU 选择结果不影响最终位置。
Patch 3 — CID kfunc NULL 表防护 (cid.c + cid.h)
核心改动:
scx_bpf_cid_topo():用READ_ONCE(scx_cid_topo)读指针,若 NULL 则输出全 −1 的SCX_CID_TOPO_NEG。scx_cid_to_cpu():用READ_ONCE(scx_cid_to_cpu_tbl)读指针,NULL 时返回-EINVAL。scx_cpu_to_cid():用READ_ONCE(scx_cpu_to_cid_tbl)读指针,NULL 时返回-EINVAL。__scx_cid_to_cpu()/__scx_cpu_to_cid()不加检查——这些只从 ops 调用路径和 enable 路径调用,表已保证分配。
ASCII 流程图
=== Patch 1: Stall 归责流程 ===
Task T (owner=Sub) stuck on Ancestor's bypass DSQ
|
v
OLD: blame Sub (owner of task) → Sub bypassing, can't act
NEW: blame Ancestor (owner of DSQ) → Ancestor must drain
+-----------+ +-----------+ +-----------+
| Task T |---->| DSQ |---->| DSQ.sched|
| owner=Sub | | (bypass) | | =Ancestor |
+-----------+ +-----------+ +-----------+
| | |
v v v
[OLD blame] [drainer] [NEW blame]
→ Sub → Ancestor → Ancestor
=== Patch 2: Bypass select_task_rq_scx ===
OLD path:
select_task_rq_scx() → scx_select_cpu_dfl() → pick CPU X
→ direct dispatch to X's local DSQ
→ but bypass enqueue redirects to bypass DSQ ★ wasted!
NEW path:
select_task_rq_scx() → bypassing? → return prev_cpu
→ bypass enqueue places on bypass DSQ (correct)
=== Patch 3: CID table NULL race window ===
Timeline during first enable:
scx_alloc_and_add_sched()
| publishes ops->priv ← BPF prog gets non-NULL sch
v
[WINDOW: tables still NULL] ← kfunc dereferences NULL ★
|
v
scx_cid_init()
| allocates tables
v
[SAFE: tables valid]
Fix: READ_ONCE(tbl) + NULL check → return -EINVAL / NEG topo
概念类比
- Patch 1:想象快递员把包裹放在了邻居家门口,包裹迟迟没人取。原来投诉系统指责包裹的主人("你的包裹怎么还不走?"),但真正该来拿包裹的是邻居(DSQ 的拥有者),因为包裹就搁在邻居的快递架上。修复后投诉指向邻居——谁家的架子就该谁来清。
- Patch 2:bypass 下的 CPU 选择就像病人已经被救护车直接送进急诊通道,挂号处还在费力选科室——选完的结果根本不会被用到,而且如果医院用了自己的分诊系统,挂号处参照的旧分类表还是冻结的。现在直接告诉挂号处"别选了,病人已经在急诊通道了"。
- Patch 3:cid 表的 NULL 竞态就像新办公室刚挂了门牌号(
ops->priv发布),员工已经能看到地址并走进来,但里面的家具(cid 查找表)还在搬运中。来的人想查座位表却撞到空墙。修法是在门口放个告示:"座位表还没到,请返回 −EINVAL"。
Highlight 突出问题
- Patch 1 的
SCX_DSQ_LOCAL特判是否完备?:当前只豁免了 local DSQ。若有其他 DSQ 类型也由 CPU 自身而非 scheduler 排空,可能需要扩展豁免集合。 - Patch 2 依赖 bypass enqueue 正确性:跳过 CPU 选择的前提是 bypass enqueue 路径必然将任务放到正确的 bypass DSQ。若 bypass enqueue 未来有变,需重新审视此 early return。
- Patch 3 只保护首次 enable 窗口:因为 cid 表分配后不再释放,所以只有 boot 后第一次 enable 有此竞态。若将来表改为动态分配/释放,保护逻辑需要扩展。
- Andrea Righi 的回复截断:第 6 封邮件内容被截断,无法确认是否有额外评审意见或 ack;需跟进查看完整回复。
版本演进
本系列为初版提交,尚无 v2。基于 sched_ext/for-7.3 (b20dfde5ec54)。
与其他相关 patch 系列的关联
- Patch 3 的
Fixes:标签指向e9b55af47edf ("sched_ext: Add topological CPU IDs (cids)")——即 cid 功能的原始引入补丁。 - Patch 1、2 均涉及 sub-scheduler bypass 机制,与近期 sched_ext 子调度器层级重构系列紧密关联。
一句话总结
三个 bugfix:修正 bypass 层级下 stall 归责对象、跳过无效的 bypass CPU 选择、防护首次 enable 窗口的 cid 表 NULL 解引用。