0/6 已展开

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-ID20260720082605.1451945-1-tj@kernel.org
来源sched-ext

明确目的

本系列修复 sched_ext 子调度器层级与拓扑 CPU ID (cid) 机制中的三个 bug:

  1. Stall 归责错位:bypass 子调度器的任务停在祖先 bypass DSQ 上,watchdog 却归责给任务所属的子调度器,而真正该排空该 DSQ 的祖先逃过了追责。
  2. Bypass 下无效 CPU 选择:bypass 时 select_task_rq_scx() 仍走默认 CPU 选择路径,但结果被 bypass enqueue 直接丢弃;若调度器自行管理 idle mask,内核 idle cpumask 已冻结,选择结果更是错的。
  3. 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)

核心改动:

  1. scx_bpf_cid_topo():用 READ_ONCE(scx_cid_topo) 读指针,若 NULL 则输出全 −1 的 SCX_CID_TOPO_NEG
  2. scx_cid_to_cpu():用 READ_ONCE(scx_cid_to_cpu_tbl) 读指针,NULL 时返回 -EINVAL
  3. scx_cpu_to_cid():用 READ_ONCE(scx_cpu_to_cid_tbl) 读指针,NULL 时返回 -EINVAL
  4. __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 突出问题

  1. Patch 1 的 SCX_DSQ_LOCAL 特判是否完备?:当前只豁免了 local DSQ。若有其他 DSQ 类型也由 CPU 自身而非 scheduler 排空,可能需要扩展豁免集合。
  2. Patch 2 依赖 bypass enqueue 正确性:跳过 CPU 选择的前提是 bypass enqueue 路径必然将任务放到正确的 bypass DSQ。若 bypass enqueue 未来有变,需重新审视此 early return。
  3. Patch 3 只保护首次 enable 窗口:因为 cid 表分配后不再释放,所以只有 boot 后第一次 enable 有此竞态。若将来表改为动态分配/释放,保护逻辑需要扩展。
  4. 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 解引用。