sched-ext discussion
[PATCHSET v9 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
LLM 分析
sched_ext 与 proxy execution 兼容化(v9)
系列概况
- 标题: [PATCHSET v9 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
- 作者: Andrea Righi arighi@nvidia.com(基于 John Stultz 早期工作)
- 版本: v9,14 个补丁
- 修改文件: kernel/sched/{core,fair,ext,sub,syscalls}.c、kernel/sched/ext/{ext.h,internal.h,sub.h}、kernel/sched/sched.h、init/Kconfig、include/linux/sched/ext.h、Documentation/scheduler/sched-ext.rst、tools/sched_ext/scx_qmap.{c,bpf.c,h}、tools/testing/selftests/sched_ext/*
- Message-ID: 20260725160513.57477-1-arighi@nvidia.com
- 完整性: 14/14 patch 完整覆盖,3 封 Sashiko AI 评审回复,无 maintainer 公开回信
补丁目的
移除 SCHED_PROXY_EXEC 与 SCHED_CLASS_EXT 的 Kconfig 互斥;把 rq->donor(调度上下文)与 rq->curr(执行上下文)拆开后对 sched_ext 做完整抽象;通过新增 SCX_OPS_ENQ_BLOCKED 让 BPF scheduler 显式接管 blocked donor;提供 enq_blocked selftest 与 scx_qmap -B 演示选项。
旧流程的问题
- 构建期互斥让发行版无法"一内核多特性"部署。
- BPF kfuncs 看到的 current 与 core 实际执行者不一致。
- DSQ 与 runqueue 双重记账错位。
- ops.running/stopping 配对不再与物理切换一一对应。
- sched_can_stop_tick 在 nr_running>1 时跳过带宽检查,受限 FAIR donor 的带宽可被绕过。
- 远端 DSQ 迁移可能在 holding_cpu 未清时切换 on_cpu / migration_disabled。
- 类切换时残留 donor 需要先 block。
新流程
[mutex waiter: donor high-prio]
|
v
[is_blocked=true, stays runnable]
|
v
[BPF: SCX_OPS_ENQ_BLOCKED set]
[ops.enqueue(p, SCX_ENQ_BLOCKED)]
[-> head-of-cid DSQ + SCX_ENQ_PREEMPT]
|
v
[core proxy-exec]
[walk owner chain, migrate donor -> owner rq]
[rq->donor = donor; rq->curr = owner]
[ops.running(donor); owner code runs]
[...mutex_release()]
[ops.stopping(donor)]
[wakeup path returns donor to wake_cpu]
Patch 概览
| # | 角色 |
|---|---|
| 01 | bugfix:迁移警告去误报 |
| 02 | bugfix:NOHZ 带宽跟随 donor |
| 03 | cleanup:sched_proxy_block_task helper |
| 04 | feature:prepare_switch() 类回调 |
| 05 | feature:sched_ext 空 hook stub |
| 06 | feature:默认拒收 EXT blocked donor |
| 07 | bugfix:running/stopping 配对 |
| 08 | refactor:reject DSQ 通用化 |
| 09 | bugfix:远端 DSQ 迁移竞态 |
| 10 | feature:curr/donor 拆分 |
| 11 | feature:ENQ_BLOCKED 主入口 |
| 12 | selftest:enq_blocked |
| 13 | 演示:scx_qmap -B |
| 14 | Kconfig 解锁 |
关键实现
Patch 01-02:核心 bugfix
Patch 01:proxy 把 donor 的调度上下文搬到 owner rq 但 wake_cpu 不变;set_task_cpu 对 migration-disabled 无条件 WARN_ON_ONCE 在 proxy 迁移或唤醒送回时假阳性。sched_proxy_exec() 且 blocked 且 task_cpu != wake_cpu 时跳过告警。
Patch 02:去掉 __need_bw_check 的 nr_running==1 限制,改用 rq->donor 检查;sched_fair_update_stop_tick 用 rq->cfs.h_nr_queued != 1。Sashiko-bot 评 High:__need_bw_check 早返回路径仍可能跳过 donor 检查。
Patch 03-04:cleanup / 新回调
Patch 03:封装 sched_proxy_block_task helper,把 runqueue 上的 donor 退回普通 blocked。
Patch 04:sched_change_begin() 接收 next_class,在 DEQUEUE_CLASS 路径中调用 next_class->prepare_switch()。Suggested-by: Tejun Heo。
Patch 05-06:sched_ext 接入点
空 stub scx_allow_proxy_exec / scx_proxy_donor_start / scx_proxy_resolved 插入 __schedule();默认对 EXT 任务返回 false;prepare_switch_scx 在类切换前 block 残留 donor。
Patch 07:running/stopping 配对
SCX_TASK_RUN_TRACKED 标志;同 donor 续跑保持 session,跨 CPU 迁移时在解析成功后再开新会话;put_prev_task_scx 中 next != p 才发出 stopping。
Patch 08:reject DSQ 通用化
原 sub-sched cap reject 路径提到 core;新增 SCX_TASK_REENQ_PROXY / SCX_TASK_REENQ_MIGRATION_DISABLED;p->scx.reject_reason 字段记录 reenqueue 原因。
Patch 09:远端迁移竞态
task_move_reject_reason() 检查 task_on_cpu / is_migration_disabled / task_current_donor,返回 SCX_TASK_REENQ_PROXY 或 MIGRATION_DISABLED;被 reject 的任务 park 在源 rq reject_dsq 等 proxy 解析后再 drain;SCX_RQ_PROXY_REENQ flag 推迟 deferred reenqueue。
Patch 10:curr/donor 拆分
sched_ext 内所有 rq->curr 替换为 rq->donor(apply_task_slice_oob、rq_owned_post_enq、enqueue_task_scx、dispatch_to_local_dsq、set_next_task_scx、first_local_task、task_tick_scx、reenq_local、run_deferred、scx_dump_cpu、kick_one_cpu、scx_bpf_task_set_slice)。kfuncs:scx_bpf_task_running / scx_bpf_cpu_curr 改返回 donor;Documentation 同步说明 proxy 下报告 donor 而非物理执行上下文。
Patch 11:SCX_OPS_ENQ_BLOCKED 主入口
scx_do_enqueue_task 计算 enq_blocked;wakeup_preempt_scx 对 enq_wakeup==false 且 blocked 且 ENQ_BLOCKED 时 resched_curr;put_prev_task_scx 中 blocked donor:next==p 直接 switch_class、有 sch 走 scx_do_enqueue_task、sch==NULL 走 local_dsq。scx_prepare_task_sched_change 在 sched_change_begin 前更新一次 rq clock(调用方需传 DEQUEUE_NOCLOCK),对未设置 ENQ_BLOCKED 的入向调度器调用 sched_proxy_block_task。scx_validate_ops 校验 ENQ_BLOCKED 必须配 ops.enqueue。
Patch 12-13:selftest 与演示
Patch 12 selftest enq_blocked:构造优先级反转(owner nice +19、donor nice -20、每 CPU nice 0 contender),同 CPU 与跨 CPU 拓扑各跑一组;BPF 在 ENQ_BLOCKED 开关前后记录 blocked-donor 入队计数与 mutex 时延;互斥通过可加载内核模块 scx_enq_blocked_test 提供。
Patch 13 scx_qmap -B:blocked donor 直接 dsq_insert(SCX_DSQ_LOCAL_ON | cid, slice, SCX_ENQ_PREEMPT),被 cap-reject 的改走 SHARED_DSQ,新增 nr_enq_blocked。策略刻意激进,仅作演示。
Patch 14:Kconfig 解锁
移除 SCHED_PROXY_EXEC 对 SCHED_CLASS_EXT 的依赖,删除原注释。
类比
把 proxy execution 想成银行柜台的"代叫号":客户 A(高优先级 donor)拿到号却发现柜员(owner)卡在复杂业务上,A 把号借给柜员继续办业务,号牌显示的还是 A。银行系统(BPF scheduler)始终看到 A 是当前被服务的客户,真正填表的却是柜员;别的客户看到柜员仍在窗口前忙碌,不知道背后换了"调度上下文"。柜员换窗口(CPU 切换)时号牌跟着挪,但叫号广播还在原窗口(wake_cpu 不变)。
Highlight:风险与注意点
- NOHZ 早返回风险(Sashiko High):__need_bw_check 早返回路径可能绕过 donor 检查。
- 被 reject 任务可反复 re-reject:reject DSQ 无重复限制,需监控 stall watchdog。
- scx_qmap -B 故意激进:blocked donor head-of-DSQ + PREEMPT,可能严重优先化 mutex 竞争任务。
- PI de-boost / root enable 时机:scx_prepare_task_sched_change 调用方必须传 DEQUEUE_NOCLOCK,否则双重更新 rq clock。
- kfunc 语义变更:scx_bpf_task_running / scx_bpf_cpu_curr 改返回 donor,依赖"owner 即 curr"的 BPF 程序可能逻辑偏差。
- 注释与代码同步(Sashiko Low):try_to_block_task 上方注释尚未更新。
- Kconfig 互斥移除仍受 !PREEMPT_RT 限制。
版本变化
v8 -> v9 主要变化(依封面给出的历史链接):
- 新增 prepare_switch() 回调(patch 04),把 sched_ext hook 拆成单独空 stub patch(patch 05)。
- 把 SCX_OPS_ENQ_BLOCKED 的 enq_blocked 路径放到 put_prev_task_scx() 统一处理"同 donor 续跑"。
- selftest 从纯 BPF 改为 BPF + 用户态 + 可加载内核模块联合驱动。
- 中期版本曾把 rq->curr 替换为 rq->donor,被 reviewer 要求补 BPF kfunc 文档化。
- 新增 reject_reason 枚举、SCX_TASK_REENQ_PROXY,把 reject DSQ 提到 core。
一句话总结
v9 通过 14 个补丁把 rq->donor 与 rq->curr 在 sched_ext 内做完整拆分,引入 SCX_OPS_ENQ_BLOCKED 让 BPF 调度器显式接管 blocked donor,从 Kconfig 层解锁 SCHED_PROXY_EXEC 与 SCHED_CLASS_EXT 的构建期互斥。