0/16 已展开

LLM 分析

sched_ext:面向子调度器的 CPU 能力委托

系列概况

  • 标题:[PATCH v3] sched_ext: Capability-based CPU delegation for sub-schedulers
  • 作者:Tejun Heo tj@kernel.org
  • 版本:v3,基于 sched_ext/for-7.3daf8e166ba59
  • 规模:36 个实际 patch
  • 修改文件:17 个,核心为 kernel/sched/ext/{sub,ext,cid,idle}.ctools/sched_ext/scx_qmap*
  • 代码统计:+4243/-496
  • Message-ID:20260707001229.1410929-1-tj@kernel.org
  • 完整性:本地标题与 cover 一致;b4 -v3 获取 36/36,未报缺件,且无 tip-bot2/自动合入邮件污染

一句话总结:把 sub-scheduler 的授权从 dispatch 扩展到 enqueue、CPU 占用、kick、抢占和 idle 通知,并以分片状态和每 CPU 快照兼顾隔离与热路径成本。

补丁目的

原有 sub-scheduler 只在 dispatch 路径由父调度器通过 scx_bpf_sub_dispatch() 自顶向下授权;enqueue 却从叶子开始,任一层都能向任一 local DSQ 插任务。父调度器因而无法划分 CPU,也无法保护自己或兄弟子树的份额。

本系列把授权表达为每个 (scheduler, cid) 的持久 capability:根拥有全部能力,父用 scx_bpf_sub_grant()/scx_bpf_sub_revoke() 向子树委托或收回。三种能力分别控制立即入队(SCX_CAP_ENQ_IMMED,亦为 SCX_CAP_BASE)、普通入队(SCX_CAP_ENQ)和跨子树抢占(SCX_CAP_PREEMPT)。高能力仅对持有者自身蕴含低能力,蕴含所得能力不能继续转授。

旧流程的问题

旧:dispatch 有父子调用链,enqueue/kick/slice/idle 却缺少统一所有权;若每次 enqueue 都向祖先逐级请求,又会让热路径产生跨 scheduler 往返和重试。

此外,准备补丁修复了几处独立但会妨碍新模型的缺陷:ops->priv 过早发布导致失败回滚时可能 UAF;BPF cmask helper 多读 slack word;BPF 可改写 arena cmask 头部,可能诱导越界;子调度器 sibling/children 链表缺少 RCU 保护;共享 kick 状态无法识别发起 scheduler。

新流程

父 scheduler grant/revoke(按 cid、逐 shard 加锁)
                 |
                 +--> 配置能力 cmask --> 合并 sub_caps_updated()
                 |
                 +--> 敲目标 CPU doorbell
                               |
                         balance() 顶部折叠
                               |
                       per-CPU effective caps
                               |
叶 scheduler insert/kick/slice ----+---- 单次 READ_ONCE 判权
                                    |允许 -> rq-owned/local DSQ
                                    `拒绝 -> reject DSQ
                                               |
                                  ops.enqueue(SCX_TASK_REENQ_CAP + 缺失能力)

新:配置更新在拓扑对齐的 shard 内串行化;目标 CPU 异步折叠为 effective-cap 快照,热路径无需遍历祖先或拿 shard 锁。revoke 还会重入队已排队任务,并把运行任务 slice 清零使其退出已失去的 CPU。

Patch 概览

Patch核心改动
1/36增加 SCX_CALL_CID_OP_TASK(),并修复嵌套 op 的 locked-rq 状态保存。
2/36extra_enq_flags 更名,明确其仅用于远端激活。
3/36延后发布 ops->priv,避免分配失败回滚与 RCU 读者形成 UAF。
4/36修复 cmask subset/equal/weight 对 slack word 的错误访问及语义差异。
5/36cmask word 操作统一使用 READ_ONCE/WRITE_ONCE
6/36scx_qmap 统一使用 u64/u32/s32 类型。
7/36cid-form verifier 禁止直接写 slice/dsq_vtime,强制经过授权 helper。
8/36异步 scx_bpf_kick_cid() 改为无返回值。
9/36kick 状态改为 per-scheduler,使判权可追溯到发起者。
10/36新增 ops.init_cids(),允许在 init 前最终确定 cid 布局。
11/36按 LLC/拓扑建立 CID shard、范围和 NUMA 节点映射。
12/36scx_bpf_cid_override() 接收并严格校验 shard 边界。
13/36把 scheduler kobject 加入 sysfs 延后到 enable work。
14/36为每个 scheduler 建立按 NUMA 节点分配的 per-shard 存储。
15/36scx_cmask_ref 快照并校验 BPF arena cmask 几何。
16/36set_cmask scratch 完全由内核可信几何构造,避免头部竞态/OOB。
17/36children/sibling 列表 RCU 化;父处于 bypass 时拒绝挂子。
18/36抽出 scx_skip_subtree_pre(),支持跳过整棵子树。
19/36实现 per-shard cap 状态以及父对子 scheduler 的 grant/revoke。
20/36新增合并触发的配置级 sub_caps_updated() 通知。
21/36建立 per-CPU effective-cap 副本与 doorbell 折叠机制。
22/36新增 effective cap 真正生效时的 sub_ecaps_updated() 通知。
23/36将 local-DSQ 处理泛化为 rq-owned DSQ。
24/36增加 reject DSQ,把能力不足的任务交还 BPF 重新选址。
25/36增加基线能力 SCX_CAP_ENQ_IMMED
26/36为每个 scheduler 实例分配唯一 ID。
27/36BPF 触发的 task slice 写入统一经过 set_task_slice()
28/36通过 slice 把 CPU 占用绑定到 SCX_CAP_BASE
29/36增加普通任务入队能力 SCX_CAP_ENQ
30/36kick 要求 BASE;无 PREEMPT 时跨子树抢占降级为普通 resched。
31/36跨 CPU move 的 insert 按实际放置任务的 scheduler 授权。
32/36update_idle() 路由给 cid owner,所有者变化时补发通知。
33/36bypass 结束后回放期间被抑制的 effective-cap 通知。
34/36增加 scx_bpf_sub_kill(),允许父驱逐失控子调度器。
35/36scx_qmap 按 cpu.weight 演示分层 CPU 划分和尾数轮转。
36/36scx_qmap 增加向无权 cid 调度的故障注入,验证内核强制执行。

关键实现

  1. 配置能力与有效能力分离:grant/revoke 修改 per-shard cmask;目标 CPU 到 balance() 才折叠到 per-CPU 副本。通知也分为配置级 sub_caps_updated() 与生效级 sub_ecaps_updated(),调用方不能假设两者同时发生。
  2. 拒绝不是丢任务:能力检查失败会把任务送进内核私有 reject DSQ,再携 SCX_TASK_REENQ_CAP 和缺失能力调用原 scheduler 的 ops.enqueue()。BPF 必须识别标记并选择仍有权的 cid。
  3. revoke 清整棵子树:子能力始终是父能力的子集;父收回某 cid 后,后代对应能力一起失效。队列任务被重入队,运行任务通过清 slice 被逐出。
  4. 并发边界:scheduler 树遍历由 RCU 保护,能力更新只竞争相关 shard;BPF arena 指针与头部先转换、快照和验界,后续尺寸计算不再信任活的 BPF 字段。
  5. 特殊路径:offline-rq 和 migration_pending insert 被强制接纳到 local DSQ 并清掉 SCX_ENQ_PREEMPT,避免 reject DSQ 弹跳与 WARN;bypass 期间暂停的通知在退出后补发。

类比

可把 cid 看作会议室、cap 看作门禁权限:总部(root)把某些房间的“立即使用、普通使用、赶走外部占用者”权限逐级授予部门。楼层(shard)各有权限台账,门口(每 CPU 快照)只放一张本地门禁表;刷卡失败者不会消失,而会被带到接待室(reject DSQ),注明缺少哪种权限后让所属部门重新安排。

Highlight:风险与注意点

  • 尚非完整 sub-scheduler 支持:Andrea Righi 在 patch 25 讨论中指出 affinity 可能只覆盖子 scheduler 无权使用的 CPU。Tejun 明确确认两大缺口:cgroup migration 尚不会把任务跨 scheduler 搬迁;任务在其 affinity 范围内没有任何获授权 CPU 时尚无 fallback。当前假设是任务不跨 cgroup,且 affinity 至少含一个有权 CPU;未来可能从 root 保留时间片兜底。
  • reject 回调若忽略 SCX_TASK_REENQ_CAP、仍选同一无权 cid,可能反复拒绝;patch 36 只提供演示/故障注入,不替所有 BPF scheduler 实现 fallback。
  • capability 更新异步生效;策略必须容忍 config 通知、各 CPU effective 通知和 idle 补发之间的时间窗口,以及 bypass 后的积压回放。
  • 仅“字面持有”的能力可继续下传;把蕴含后的有效集合直接复制给子 scheduler 会破坏委托不变量。
  • per-shard 降低全局锁竞争但不消除热点:大量 scheduler 同时修改同一 shard 时仍串行,grant/revoke 还会产生 per-CPU doorbell、任务逐出和通知成本。
  • b4 从讨论中为 patch 1–8 收集到 Andrea Righi 的 Reviewed-by;patch 25 有实质性问题讨论,其余页面所列回复多为作者跟进。不能据此宣称整个 36-patch 系列已获一致认可或已合入。

版本变化

v3 相对 v2 的重点是收紧竞态和失败路径:校验 scx_cmask_ref.alloc_words 并删除依赖 BPF 可写计数的 __counted_by;bypass 父下挂子改为 -EBUSY;pshard 数组完整构造后才发布并用 READ_ONCE 读取;通知使用私有栈和已验证 cmask;修复 offline/migration insert 弹跳、pending slice 丢失,以及 scx_qmap 的计时、快照和 SCX_ENQ_IMMED 传递。cover 同时给出 v1、v2 链接,当前线程未出现 v4。

一句话总结

该系列用“分片配置状态 + 每 CPU 有效快照 + reject DSQ 重试”补齐 sched_ext 子调度器的 CPU 隔离主干,但 cgroup 跨 scheduler 迁移和无可用授权 CPU 的强制 fallback 仍是上线前必须补齐或由策略规避的边界。