0/20 已展开

LLM 分析

sched_ext:让 proxy execution 与 sched_ext 共存(v11)

系列概况

  • 标题[PATCHSET v11 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
  • 作者:Andrea Righi <arighi@nvidia.com>
  • 版本:v11,基于 John Stultz 早期工作,已迭代 11 版
  • 规模:15 个 patch + cover letter
  • 修改文件kernel/sched/{core,fair,ext,sched.h}include/linux/sched/ext.hinit/KconfigDocumentation/scheduler/sched-ext.rsttools/sched_ext/scx_qmap.{c,bpf.c,h}tools/testing/selftests/sched_ext/{enq_blocked.c,enq_blocked.bpf.c,test_modules,Makefile,config,.gitignore}
  • 代码统计:核心 + selftest 新增 >1000 行;Kconfig 删 2 行
  • Message-ID20260810151523.86994-1-arighi@nvidia.com-16
  • 完整性:完整,cover letter 含背景、设计、示例输出,附新 kselftest 与 qmap 演示

补丁目的

解除 CONFIG_SCHED_PROXY_EXECCONFIG_SCHED_CLASS_EXT 的构建时互斥,让发行版和终端用户能在同一内核中按需组合特性。核心思路:把"调度上下文(donor)"和"执行上下文(lock owner)"在 sched_ext 视图下分离,由 BPF 通过新 ops flag SCX_OPS_ENQ_BLOCKED 决定是否参与。

旧流程的问题

  1. 构建期强制互斥:depends on !SCHED_CLASS_EXT,发行版只能二选一。
  2. 任务视角不一致:sched_ext 假设 rq->curr 就是被调度任务;proxy 把 rq->curr 换成 owner,BPF 视角会"看到另一个 task"。
  3. 回调配对错位:proxy 同 CPU 切换 owner 时,会触发多余的 running/stopping 事件。
  4. NOHZ 带宽检查漏洞:__need_bw_check() 限定 rq->nr_running == 1,被 throttle 的 FAIR donor 会漏掉 enforcement。
  5. set_task_cpu 误报:proxy 把 donor 调度上下文搬到 owner CPU 时,is_migration_disabled 检查会触发 WARN。
  6. reject DSQ 语义单一:只覆盖 sub-scheduler cap 失败,无法表达其他临时 placement 失败。

新流程

  • PATCH 01-04:核心内核准备,包括 split scheduler/execution context 检查、迁移警告豁免、sched_change_begin(next_class)、新增 sched_proxy_block_task()
  • PATCH 05-07:sched_ext 接入 scx_allow_proxy_exec()scx_proxy_donor_start()scx_proxy_resolved(),默认让 EXT 任务走普通 block,并用新 SCX_TASK_RUN_TRACKED 修回调配对。
  • PATCH 08-10:reject DSQ 从 sub.c 移到 ext.c 并通用化;引入 SCX_TASK_REENQ_PROXYtask_move_proxy_raced(),处理 remote DSQ 转移与 proxy 竞态。
  • PATCH 11-12:rq->curr / rq->donor 拆分;新增 SCX_OPS_ENQ_BLOCKED + SCX_ENQ_BLOCKED,让 BPF 自主决定 blocked donor 的命运。
  • PATCH 13-14:新增 enq_blocked selftest(priority inversion × same/cross CPU)与 scx_qmap -X 演示选项。
  • PATCH 15:移除 Kconfig 限制。

关键实现

1. 拆分调度/执行上下文

/* PATCH 01: use rq->donor for bandwidth check */
if (__need_bw_check(rq->donor) && cfs_task_bw_constrained(rq->donor))
    return false;

让 nohz tick 即使在 owner 处于其他调度类时也能正确看到被节流的 donor。

2. running session 追踪

/* PATCH 07: SCX_TASK_RUN_TRACKED flag */
static void scx_start_task_running(struct rq *rq, struct task_struct *p)
{
    if (p->scx.flags & SCX_TASK_RUN_TRACKED)
        return;
    if (SCX_HAS_OP(sch, running))
        SCX_CALL_OP_TASK(sch, running, rq, p);
    p->scx.flags |= SCX_TASK_RUN_TRACKED;
}

保证 blocked donor 在 proxy resolution 完成前不暴露 ops.running();迁移后旧 CPU 上的 session 正确结束。

3. BPF 决策入队

/* PATCH 12: enq_blocked decision in scx_do_enqueue_task */
enq_blocked = (sch->ops.flags & SCX_OPS_ENQ_BLOCKED)
           && p->is_blocked && !(enq_flags & SCX_ENQ_WAKEUP);
if (enq_blocked)
    enq_flags |= SCX_ENQ_BLOCKED;

只有声明 SCX_OPS_ENQ_BLOCKED 的 BPF 才会收到 blocked donor 的入队回调。

4. reject DSQ 竞态

/* PATCH 10: park raced task on source rq */
static void scx_reject_task(...)
{
    p->scx.holding_cpu = -1;
    p->scx.flags |= SCX_TASK_REENQ_PROXY;
    scx_dispatch_enqueue(sch, rq, &rq->scx.reject_dsq, p, 0, 0, enq_flags);
}

remote DSQ 转移若撞上 proxy 状态,把任务留在源 CPU 排队,等 proxy resolution 后再重新入队。

ASCII 流程图

1) donor to owner proxy dispatch

+-----------+    proxy-exec     +-----------+
|   donor   | ---sched ctx---> |   owner   |
|  (FAIR)   |                   | (any cls) |
+-----------+                   +-----------+
      |                              ^
      | SCX_ENQ_BLOCKED              | real exec
      v                              |
+----------+    dispatch         +--------+
|   BPF    | ------------------> | rq.curr |
| scheduler|                     | = owner |
+----------+                     +--------+
      |
      | rq->donor = donor
      v
slice/vtime on donor; owner only runs code

2) running session state transition

   pick_next_task()
        |
        v
+--[DONOR not BLOCKED]--> scx_start_task_running()
|                            | SCX_TASK_RUN_TRACKED = 1
|                            v
|                       ops.running()
|                            |
|   same CPU, next != p      |
|   (proxy same-cpu owner    v
|    switch) put_prev_task_scx()
|    keeps RUN_TRACKED, no stopping
|                            |
|     owner leaves CPU       |
|     proxy_resolved updates v
+--------------------> next dequeue: emit stopping
                         clear RUN_TRACKED

3) reject DSQ decision flow

remote DSQ move attempt
    |
    v
lock source rq
    |
    v
task_move_proxy_raced(p)?
    |
    +--yes--> scx_reject_task()
    |             |
    |             v
    |        reject_dsq
    |             |
    |             v
    |        SCX_TASK_REENQ_PROXY
    |             |
    |             v
    |   after proxy resolution:
    |   reject_dsq drain re-enqueues
    |   task back to owning BPF
    |
    +--no---> normal dispatch

类比

把 proxy execution 想象成"替身演员"机制:donor 是剧组签约主角,预算和积分都挂在主角名下;实际拍摄时让替身(lock owner)上场跑动作。导演 BPF 仍按主角对剧本的需求调度,剧务看到的也是"主角是当前档期的人",但镜头前跑龙套的是替身。即便替身临时换人、跨组拍摄,账单和 KPI 始终走主角,sched_ext 的 running/stopping/tick 不会因替身走位而误记一次进度。

Highlight:风险与注意点

  • sashiko-bot 高危提示:NOHZ_FULL 隔离下,PATCH 01 把 RT 抢占视为"donor 检查",可能让本该停 tick 的 RT 上下文被强制打开 tick,需实测 NOHZ_FULL 配置。
  • 行为变更面大reject_dsq 从可选变为通用,root scheduler 也走这条路径,需要确认现有 BPF 程序不会对 SCX_TASK_REENQ_REASON 之外的标志产生依赖。
  • future work 留口:compatible RT/DL PI 转换如何保留 proxy session 仍是 TODO,保守策略会以"重置"导致部分 boosted donor 失去捐赠。
  • is_migration_disabled 边界:PATCH 02 放宽警告,可能掩盖真正的迁移违规;建议加 debug 计数器验证。
  • 多次版本迭代:从 v1 走到 v11,集成范围扩张,回滚成本上升;review 反馈集中在 PATCH 01/09/10/11,需重点审视。
  • 测试覆盖enq_blocked 主查 same/cross-CPU 优先级反转,但未覆盖 NUMA/affinity 与 cgroup 边界场景。

版本变化

cover letter 列出从早期 Stultz 原型到 v10 的 lore 链接共 12 个版本。v11 主要针对前期 review 的清理:把 reject DSQ 从 sub.c 抽到核心、修正 running session 配对、拆分 kfuncs 的 donor 语义。本系列追踪难度大,建议 diff 各版 cover letter 对照。

一句话总结

v11 通过让 sched_ext 把"调度上下文(donor)"与"执行上下文(owner)"分离并开放 SCX_OPS_ENQ_BLOCKED 让 BPF 自主决策,从而解开 SCHED_PROXY_EXECSCHED_CLASS_EXT 的构建时互斥。