0/28 已展开

LLM 分析

sched/proxy-exec + sched_ext:让两个调度特性一起跑

系列概况

  • 标题[PATCHSET v8 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
  • 作者:Andrea Righi arighi@nvidia.com,基于 John Stultz 早期工作
  • 版本:PATCHSET v8(2026-07-21)
  • 规模:12 个 patch([PATCH 01/12] - [PATCH 12/12]
  • 修改文件kernel/sched/core.ckernel/sched/sched.hkernel/sched/syscalls.ckernel/sched/ext/ext.ckernel/sched/ext/ext.hkernel/sched/ext/internal.hkernel/sched/ext/sub.cinit/Kconfiginclude/linux/sched/ext.hDocumentation/scheduler/sched-ext.rsttools/sched_ext/{scx_qmap.bpf.c,.c,.h}tools/testing/selftests/sched_ext/{Makefile,config,enq_blocked.{c,bpf.c},test_modules/}
  • 代码统计:仅 PATCH 06 就 79+/10- 行;PATCH 07 跨多文件把 rq->curr 切到 rq->donor;选编规模约 700 行
  • Message-ID20260721063242.552774-1-arighi@nvidia.com
  • 完整性:完整——cover letter、12 patches、John Stultz / Tejun Heo / sashiko-bot 的逐 patch 反馈齐备

补丁目的

解除 CONFIG_SCHED_PROXY_EXECCONFIG_SCHED_CLASS_EXT 的 build-time 互斥,让发行版能用一个内核同时启用两者。proxy execution 把 mutex 等待者(donor)的调度上下文借给持有者(owner)代为执行;sched_ext 是 BPF 驱动的高度可编程调度器。两者在 set_task_cpu()sched_can_stop_tick()ops.running()/stopping()scx_bpf_*_curr() 一系列交点上假设的是同一对 (rq->curr, running),proxy 引入的 donor/curr 分裂正好把这些假设逐一打破。系列修复这些假设被打破后冒出来的 WARM 假告警、迁移竞态、NOHZ 带宽核算偏差、回调不成对、kfunc 误判,最后通过删 Kconfig 限制打开同编译。

旧流程的问题

  1. SCHED_PROXY_EXEC depends on !SCHED_CLASS_EXT——内核必须挑一个。
  2. set_task_cpu()is_migration_disabled(p) 无条件 WARN,proxy 搬调度上下文(不搬执行上下文)触发假正例。
  3. sched_can_stop_tick()rq->curr + 要求 nr_running == 1;proxy 下受限 FAIR donor 漏算 tick 可能停掉。
  4. consume_remote_task() 用 holding_cpu 握手防迁移;proxy 期间 owner 已上线但 holding_cpu 未清,导致 sleeping-while-atomic + lockdep corruption。
  5. ops.running() 对一个根本不会跑的 blocked donor 仍然触发,紧随的 ops.stopping() 仍会调,形成不配对。
  6. BPF kfunc scx_bpf_task_running/cpu_curr/cid_curr 返回 rq->curr——即 owner,不是 BPF 调度的 donor。

新流程

  • 默认行为:保留型 donor 在调度类切换边界(__schedule()rt_mutex_setprio()__sched_setscheduler()、root 启用)被 scx_prepare_setscheduler() / scx_prepare_task_sched_change() 强制 block。
  • opt-in:声明 SCX_OPS_ENQ_BLOCKED 的 BPF 调度器可以保留 blocked donor;任务走 ops.enqueue(SCX_ENQ_BLOCKED),由 BPF 决定 dispatch / 抢占。
  • 视角统一:rq->donor 是 BPF 视角的"current",rq->curr 仅 core 内部表示 owner。
  • running()/stopping()SCX_TASK_RUN_TRACKED 严格配对。
  • consume_remote_task() 拿到 src rq 锁后二次复核 on-cpu / migration-disabled / active donor,竞态时 GDSQ fallback 而非 scheduler error;用 dsq->seq 快照防止 retry 死循环。

Patch 概览

  • PATCH 01 sched/core:去掉 proxy donor 上 migration-disabled WARN。
  • PATCH 02 sched:NOHZ CFS 带宽改看 rq->donor 并取消 nr_running == 1 限制。
  • PATCH 03 sched:新增 sched_proxy_block_task() 助手。
  • PATCH 04 sched_ext:默认拒绝保留型 donor;批量加 scx_prepare_* / scx_allow_proxy_exec()
  • PATCH 05 sched_ext:用 SCX_TASK_RUN_TRACKED 修 running/stopping 不配对。
  • PATCH 06 sched_ext:fix consume_remote_task() 中 holding_cpu 竞态;引入 task_can_move_from_locked_rq()
  • PATCH 07 sched_ext:批量把 rq->curr 视角改为 rq->donor,kfunc + 文档同步。
  • PATCH 08 sched_ext:blocked donor 迁移时检查 task_current_donor(),活跃 donor 不能搬。
  • PATCH 09 sched_ext:新增 SCX_OPS_ENQ_BLOCKED + SCX_ENQ_BLOCKED,blocked donor 入队委派给 BPF。
  • PATCH 10 sched_ext:新增 enq_blocked kselftest。
  • PATCH 11 sched_ext:scx_qmap 加 -B 选项打开 SCX_OPS_ENQ_BLOCKED 支持。
  • PATCH 12 sched:从 SCHED_PROXY_EXEC 移除 depends on !SCHED_CLASS_EXT

关键实现

/* PATCH 07: donor/curr 一分为二,BPF kfunc 看 donor */
static bool task_can_move_from_locked_rq(struct task_struct *p)
{
        struct rq *src_rq = task_rq(p);

        lockdep_assert_rq_held(src_rq);

        if (!sched_proxy_exec())
                return true;

        /* PATCH 08: 活跃 donor 不能搬 */
        if (task_current_donor(src_rq, p))
                return false;

        if (task_on_cpu(src_rq, p))
                return false;

        return !is_migration_disabled(p);
}

/* PATCH 07: BPF 看到的是 donor,不是物理 owner */
__bpf_kfunc bool scx_bpf_task_running(const struct task_struct *p)
{
        return rcu_access_pointer(task_rq(p)->donor) == p;
}
+----------------------+            +----------------------+
|   BPF scheduler view |            |     core view        |
| (rq->donor = VIP)    |            | (rq->curr = owner)   |
+----------------------+            +----------------------+
            |                                 |
            | slice / vtime owned by donor    | actually executing
            | decides dispatch / preempt      | walks blocked_on
            |                                 | chain, runs owner
            v                                 v
   SCX_OPS_ENQ_BLOCKED on?  --yes-->  ops.enqueue(SCX_ENQ_BLOCKED)
                       |
                       no
                       v
            scx_allow_proxy_exec() == false
            -> donor goes normal block path

   blocked donor --> keep on rq --> find_proxy_task()
                                       |
                                       v
                              run owner with donor ctx

类比

proxy execution 像 VIP 客人(donor)把自己的排队号(优先级/slice)借给柜员,让柜员先去招呼后面的普通客户(owner),VIP 的预算仍归自己消耗。sched_ext 是定制化排号规则——他要的是"号的主人",而不是被柜员当下招呼的客人,否则就没法决定 VIP 何时插队、何时让号闲置。所以 rq->donor 是 VIP,rq->curr 是当下招呼的普通客户;BPF 通过 rq->donor 视角看 running,SCX_OPS_ENQ_BLOCKED 像 VIP 在门口登记"我愿意把号借出"——没登记的 VIP 不会让号留在柜面。

Highlight:风险与注意点

  • PATCH 06 的 GDSQ_FALLBACK:Tejun 指出把被拒任务打到全局 DSQ 会在普通 mutex 竞争下高频触发并绕过 BPF placement,v9 需要换策略。
  • PATCH 02 残留:sashiko 提示 sched_fair_update_stop_tick() 里还有 nr_running != 1 限制没被改,proxy 路径下仍可能漏算。
  • PATCH 04 直接调 ext:Tejun 建议用 sched class prepare_switch() callback 走 sched_change_begin(),作者已认账,下版加前置 refactor。
  • PATCH 06 → PATCH 08 顺序:sashiko 标 PATCH 06 缺 active-donor 检查,靠 PATCH 08 补;需要保证 bisect 不会落到缺这一步的中间提交。
  • kselftest 依赖 CONFIG_EXPERT=y:默认发行配置可能没开,CI 要确认是否跑或明确 skip。
  • scx_qmap -B:激进策略刻意偏向 mutex 持有者,mutex_hold_delta / mutex_wait_delta 解读时不能只看平均。
  • donor/curr 拆分是 BPF 可见 ABI 变化,老 BPF 程序在 proxy 下做错 placement 的可能性上升,需要文档/运行时审计。
  • John 在 stress-ng / locktorture 下偶发 sched_ext 因 stall 被关闭,需要 v9 在本系列之外进一步确认相关性。

版本变化

v7 → v8 关键改动(多轮 review 后的累计差异):

  • 引入 SCX_TASK_RUN_TRACKEDSCX_TASK_ENQ_WAKEUP 两个 task flag 严格配对 running/stopping 与 wakeup 跟踪。
  • 引入 ops flag SCX_OPS_ENQ_BLOCKED 与 enq flag SCX_ENQ_BLOCKED,让 BPF 显式表态接收 blocked donor。
  • task_can_move_from_locked_rq() 在 PATCH 06 引入、在 PATCH 08 由 active-donor 检查补强。
  • 把散落的 inline block 逻辑收敛到 sched_proxy_block_task() 助手。
  • 新增 enq_blocked kselftest 取代早期手工验证。
  • sched-ext.rst 文档新增 "blocked donor 不进 running/stopping 会话" 段落。

一句话总结

12 patch 的 v8 系列解除了 SCHED_PROXY_EXEC 与 SCHED_CLASS_EXT 的 build-time 互斥,通过拆分 rq->donor / rq->curr、引入 SCX_OPS_ENQ_BLOCKED 让 BPF 显式表态接收 blocked donor,并修复了 proxy 期间引入的迁移竞态、NOHZ 带宽假判、running/stopping 错配与 BPF kfunc 误判。