0/14 已展开

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-ID20260718081727.582037-1-tj@kernel.org
来源sched-ext 频道
改动规模9 文件, +819 / -234 行

明确目的

此系列修复两个子调度器拓扑下的核心缺陷:

  1. Cgroup 迁移破坏 sched 不变量:任务的 p->scx.sched 必须与其 cgroup 的 cgrp->scx_sched 一致,但迁移跨子调度器边界后任务仍留在旧 sched,导致错误调度和 sched 被释放后的 use-after-free

  2. cgroup op 总是投递到 root schedcgroup_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_disablescx_fail_parent 中提取两个辅助函数:

  • scx_rehome_task(to, p):任务已在 to 上完成 init,直接切换 sched 并将状态覆盖为 READY
  • scx_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() 把任务切换到目标 sched
  • scx_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 返回 root
  • scx_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;第二遍从父 sched cgroup_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].weightredistribute()。删除用户空间的 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 窗出问题

  1. Use-after-free 是真实安全风险:cgroup 跨子调度器边界迁移后 stale sched 指针在被释放后仍被引用,不仅是功能错误而是内核安全漏洞。

  2. MIGRATE_CANCELED 回滚的逆向遍历顺序:cgroup 代码用 list_for_each_entry_continue_reverselist_for_each_entry_reverse 回滚已通知的任务,保证只有成功 MIGRATING 的任务收到 CANCELED。顺序错误会导致遗漏回滚。

  3. __scx_init_task() 的 cgrp 参数语义:迁移前 task_group(p) 仍是源 cgroup,必须显式传入目标 cgrp;其他路径传 NULL 让函数自行从 task_group(p) 推导。容易误用。

  4. Tejun 拒绝 Fixes tag:Andrea Righi 建议加 Fixes: 337ec00b1d9c,但 Tejun 只加 Closes——因为该 feature 本身未完整(restricted affinity 问题尚存),且中间有大量代码变更,不想触发 stable 自动 backport。这是合理的工程判断。

  5. cgroup1 vs cgroup2 的拓扑差异:cpu controller 可挂 cgroup1,此时 task_group 层级与 cgroup2 不同,tg 可能指向 root sched 而非子调度器。scx_tg_knob_sched() 通过 cgroup_on_dfl() 判断处理此差异。

  6. 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。