sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Cgroup migration and op delivery for sub-schedulers
LLM 分析
sched_ext: Cgroup 迁移与子调度器 op 交付
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | sched_ext: Cgroup migration and op delivery for sub-schedulers |
| 作者 | Tejun Heo |
| 版本/规模 | 8 patches, 基于 sched_ext/for-7.3 (7c2cd767705d) |
| Message-ID | 20260718081727.582037-1-tj@kernel.org |
| 来源 | sched-ext 频道 |
| 改动规模 | 9 文件, +819 / -234 行 |
明确目的
此系列修复两个子调度器拓扑下的核心缺陷:
-
Cgroup 迁移破坏 sched 不变量:任务的
p->scx.sched必须与其 cgroup 的cgrp->scx_sched一致,但迁移跨子调度器边界后任务仍留在旧 sched,导致错误调度和 sched 被释放后的 use-after-free。 -
cgroup op 总是投递到 root sched:
cgroup_init/exit、knob op、cgroup_move等无论 cgroup 属于哪个子调度器都只发给 root,子调度器无法感知自己管理的 cgroup 事件。
遍历代码
Patch 1/8 — cgroup 新增 task migration notifier
在 cgroup 子系统引入 cgroup_task_notifier,提供三个事件:
CGROUP_TASK_MIGRATING:迁移提交前触发,可 veto(返回错误则整个迁移失败)CGROUP_TASK_MIGRATED:迁移提交后触发,不可失败CGROUP_TASK_MIGRATE_CANCELED:迁移失败后回滚,通知已成功 MIGRATING 的任务
struct cgroup_task_migrate_ctx {
struct task_struct *task;
struct cgroup *src_dcgrp; // MIGRATED 时为 NULL
struct cgroup *dst_dcgrp;
};
只有改变 dfl cgroup 的迁移才触发通知。blocking_notifier_call_chain_robust() 保证 MIGRATE_CANCELED 在失败时自动回滚已通知的任务。
Patch 2/8 — 重构 scx_rehome_task / scx_punt_task
从 scx_sub_disable 和 scx_fail_parent 中提取两个辅助函数:
scx_rehome_task(to, p):任务已在to上完成 init,直接切换 sched 并将状态覆盖为 READYscx_punt_task(to, p):将任务放到一个失败的、已 bypass 的 sched 上,状态为 NONE,等待该 sched teardown 重新安置
无功能变更,纯重构。
Patch 3/8 — scx_cgroup_enabled 移出 CONFIG_EXT_GROUP_SCHED
将 scx_cgroup_enabled 的定义和赋值从 #ifdef CONFIG_EXT_GROUP_SCHED 块移到外面。因为 cgroup 迁移 re-home 路径需要此 gate,但迁移逻辑不在 group sched 配置块中。
Patch 4/8 — 核心:cgroup 迁移时 re-home 任务
注册 cgroup_task_notifier,三个回调:
scx_cgroup_task_migrating():在目标 sched 上执行 fallible__scx_init_task(),失败则 veto 迁移(cgroup.procs 写入返回 errno)scx_cgroup_task_migrated():迁移成功后调用scx_rehome_task()把任务切换到目标 schedscx_cgroup_task_migrate_canceled():迁移失败时调用scx_sub_init_cancel_task()撤销目标 sched 的 init
__scx_init_task() 新增 cgrp 参数:迁移前 task_group 仍是源的,需要显式传入目标 cgroup 给 ops.init_task()。
Patch 5/8 — cgroup op 按 task_group 路由到对应 sched
新增 scx_tg_sched(tg) 和 scx_tg_knob_sched(tg):
scx_tg_sched():返回tg->scx.sched(init 成功的 sched),autogroup 返回 rootscx_tg_knob_sched():返回父 tg 的 sched——因为 knob 属于父 cgroup,在子调度器 attach 点处 knob 应投递给父 sched
所有 cgroup op(init/exit、set_weight/idle/bandwidth、prep_move/move/cancel_move)现在使用对应 sched 而非固定 root。cgroup_move 只投递到不触发 re-home 的迁移(re-home 的迁移由 init_task/exit_task pair 通知)。
Patch 6/8 — 子调度器 enable/disable 时 cgroup 权属交接
- enable(
scx_cgroup_claim_subtree):两遍扫描子树 cgroup——第一遍在子 sched 上cgroup_init,标记SCX_TG_SUB_INIT;第二遍从父 schedcgroup_exit,移交tg->scx.sched = sch。失败时只回滚子 sched 的 init,父 sched 不受影响。 - disable(
scx_cgroup_return_subtree):逆向操作,cgroup 从子 sched exit 后 init 回父 sched。父 init 失败则父也 fail,cgroup 继续往上移交,最终到 root。
scx_tg_sched() 现在检查 SCX_TG_INITED:init 失败的 tg 返回 NULL,各 op 跳过 NULL sched。
Patch 7/8 — scx_qmap 使用 cgroup_set_weight 替代用户空间轮询
ops.cgroup_set_weight() 现在投递到父 sched,父 qmap 收到子 sched attach 点的 weight 变更,直接在内核更新 sub_sched_ctxs[i].weight 并 redistribute()。删除用户空间的 feed_weights() 轮询和 repartition PROG_RUN poke。sub_attach 时通过 bpf_cgroup_from_id() 种子当前 weight。
Patch 8/8 — scx_qmap 故障注入模式
新增 -J init-fail(让 init_task 对 comm 为 "qmfail*" 的任务返回 -ENOMEM)和 -J cgrp-init-fail(让 cgroup_init 对名为 "qmfail*" 的 cgroup 返回 -ENOMEM)。前者测试迁移 veto 路径,后者测试权属回交失败路径。
ASCII 流程图
Cgroup Migration Re-home Flow
=============================
cgroup.procs write
|
v
[CGROUP_TASK_MIGRATING] ──► scx_cgroup_task_migrating()
| |
| v
| dst sched: __scx_init_task()
| |
| success / fail
| / \
| v v
| NOTIFY_OK veto errno
| | |
v v v
commit migration [MIGRATED] [MIGRATE_CANCELED]
| | |
v v v
[CGROUP_TASK_MIGRATED] ──► rehome_task() undo init
(can't fail) (cancel_task)
Sub-scheduler Enable Cgroup Handover
====================================
parent sched sub sched (enabling)
| |
| Pass 1: cgroup_init() |
|◄─────────────────────────────|
| mark SCX_TG_SUB_INIT |
| |
| Pass 2: cgroup_exit() |
|◄─────────────────────────────|
| tg->scx.sched = sub |
| clear SUB_INIT |
v v
parent no longer owns sub now owns subtree cgroups
Failed Re-init Cascade (disable path)
=====================================
sub (dying) ─► parent (re-init fails) ─► grandparent ─► ... ─► root
return return return final owner
概念类比
想象一栋大楼有多层管理:每层有自己的楼层经理(子调度器),整栋楼有总经理(root sched)。每间办公室(cgroup)归其所在楼层的经理管理。
迁移 bug:员工(任务)从 3 楼搬到 5 楼,但工牌上仍然写着"3 楼经理"——5 楧经理不认识他,3 楼经理已经不管他了。如果 3 楧经理离职(sched freed),工牌指向一个不存在的人,就是 use-after-free。
修复:搬家公司(cgroup_task_notifier)在搬迁前先通知新楼层经理做入职审查(init_task),如果审查不通过搬迁取消;搬迁完成后正式换发工牌(rehome_task);搬迁取消时撤销入职登记(cancel_task)。
op 路由:以前所有办公室的装修申请(cgroup op)都只交总经理,楼层经理看不到自己楼层的事。现在装修申请交给负责该楼层的经理,预算调整(knob)交给上一层经理审批。
权属交接:新楼层经理上任时,该楼层所有办公室从上层经理逐个过户到新经理(先入职再退职,保证失败时上层不受影响);经理离职时办公室退回上层,上层 init 失败则继续往上退,最终退到总经理。
Highlight 窗出问题
-
Use-after-free 是真实安全风险:cgroup 跨子调度器边界迁移后 stale sched 指针在被释放后仍被引用,不仅是功能错误而是内核安全漏洞。
-
MIGRATE_CANCELED 回滚的逆向遍历顺序:cgroup 代码用
list_for_each_entry_continue_reverse和list_for_each_entry_reverse回滚已通知的任务,保证只有成功 MIGRATING 的任务收到 CANCELED。顺序错误会导致遗漏回滚。 -
__scx_init_task()的 cgrp 参数语义:迁移前task_group(p)仍是源 cgroup,必须显式传入目标 cgrp;其他路径传 NULL 让函数自行从task_group(p)推导。容易误用。 -
Tejun 拒绝 Fixes tag:Andrea Righi 建议加
Fixes: 337ec00b1d9c,但 Tejun 只加 Closes——因为该 feature 本身未完整(restricted affinity 问题尚存),且中间有大量代码变更,不想触发 stable 自动 backport。这是合理的工程判断。 -
cgroup1 vs cgroup2 的拓扑差异:cpu controller 可挂 cgroup1,此时 task_group 层级与 cgroup2 不同,tg 可能指向 root sched 而非子调度器。
scx_tg_knob_sched()通过cgroup_on_dfl()判断处理此差异。 -
re-home 与 disable 的竞态安全:
scx_cgroup_task_migrated()注释说明对目标 sched 的任何销毁阶段都安全——disable 在scx_cgroup_lock()下重置 cgroup ownership 并 re-home 任务,与 MIGRATED 互斥。
版本演进
此系列为首次发布(无 vN 标记),直接基于 sched_ext/for-7.3。Tejun 已合并全系列(message 12)。
与其他系列的关联
此系列是子调度器 cgroup 支持的补完部分,接续 337ec00b1d9c("Implement cgroup sub-sched enabling and disabling")。Tejun 指出 restricted affinity 问题仍待解决,后续还会有相关补丁。
一句话总结
修复 cgroup 迁移跨子调度器边界时的 use-after-free 和 op 路由错误,通过新增 cgroup task notifier 实现迁移三阶段(fallible init → commit re-home → cancel undo),并将 cgroup op 按 task_group topology 投递到对应 sched。