sched-ext discussion
[PATCHSET v3 sched_ext/for-7.3] sched_ext: Capability-based CPU delegation for sub-schedulers
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.3的daf8e166ba59 - 规模:36 个实际 patch
- 修改文件:17 个,核心为
kernel/sched/ext/{sub,ext,cid,idle}.c与tools/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/36 | extra_enq_flags 更名,明确其仅用于远端激活。 |
| 3/36 | 延后发布 ops->priv,避免分配失败回滚与 RCU 读者形成 UAF。 |
| 4/36 | 修复 cmask subset/equal/weight 对 slack word 的错误访问及语义差异。 |
| 5/36 | cmask word 操作统一使用 READ_ONCE/WRITE_ONCE。 |
| 6/36 | scx_qmap 统一使用 u64/u32/s32 类型。 |
| 7/36 | cid-form verifier 禁止直接写 slice/dsq_vtime,强制经过授权 helper。 |
| 8/36 | 异步 scx_bpf_kick_cid() 改为无返回值。 |
| 9/36 | kick 状态改为 per-scheduler,使判权可追溯到发起者。 |
| 10/36 | 新增 ops.init_cids(),允许在 init 前最终确定 cid 布局。 |
| 11/36 | 按 LLC/拓扑建立 CID shard、范围和 NUMA 节点映射。 |
| 12/36 | scx_bpf_cid_override() 接收并严格校验 shard 边界。 |
| 13/36 | 把 scheduler kobject 加入 sysfs 延后到 enable work。 |
| 14/36 | 为每个 scheduler 建立按 NUMA 节点分配的 per-shard 存储。 |
| 15/36 | 用 scx_cmask_ref 快照并校验 BPF arena cmask 几何。 |
| 16/36 | set_cmask scratch 完全由内核可信几何构造,避免头部竞态/OOB。 |
| 17/36 | children/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/36 | BPF 触发的 task slice 写入统一经过 set_task_slice()。 |
| 28/36 | 通过 slice 把 CPU 占用绑定到 SCX_CAP_BASE。 |
| 29/36 | 增加普通任务入队能力 SCX_CAP_ENQ。 |
| 30/36 | kick 要求 BASE;无 PREEMPT 时跨子树抢占降级为普通 resched。 |
| 31/36 | 跨 CPU move 的 insert 按实际放置任务的 scheduler 授权。 |
| 32/36 | update_idle() 路由给 cid owner,所有者变化时补发通知。 |
| 33/36 | bypass 结束后回放期间被抑制的 effective-cap 通知。 |
| 34/36 | 增加 scx_bpf_sub_kill(),允许父驱逐失控子调度器。 |
| 35/36 | scx_qmap 按 cpu.weight 演示分层 CPU 划分和尾数轮转。 |
| 36/36 | scx_qmap 增加向无权 cid 调度的故障注入,验证内核强制执行。 |
关键实现
- 配置能力与有效能力分离:grant/revoke 修改 per-shard cmask;目标 CPU 到
balance()才折叠到 per-CPU 副本。通知也分为配置级sub_caps_updated()与生效级sub_ecaps_updated(),调用方不能假设两者同时发生。 - 拒绝不是丢任务:能力检查失败会把任务送进内核私有 reject DSQ,再携
SCX_TASK_REENQ_CAP和缺失能力调用原 scheduler 的ops.enqueue()。BPF 必须识别标记并选择仍有权的 cid。 - revoke 清整棵子树:子能力始终是父能力的子集;父收回某 cid 后,后代对应能力一起失效。队列任务被重入队,运行任务通过清 slice 被逐出。
- 并发边界:scheduler 树遍历由 RCU 保护,能力更新只竞争相关 shard;BPF arena 指针与头部先转换、快照和验界,后续尺寸计算不再信任活的 BPF 字段。
- 特殊路径:offline-rq 和
migration_pendinginsert 被强制接纳到 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 仍是上线前必须补齐或由策略规避的边界。