sched-ext discussion
[PATCHSET v8 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
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.c、kernel/sched/sched.h、kernel/sched/syscalls.c、kernel/sched/ext/ext.c、kernel/sched/ext/ext.h、kernel/sched/ext/internal.h、kernel/sched/ext/sub.c、init/Kconfig、include/linux/sched/ext.h、Documentation/scheduler/sched-ext.rst、tools/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-ID:
20260721063242.552774-1-arighi@nvidia.com - 完整性:完整——cover letter、12 patches、John Stultz / Tejun Heo / sashiko-bot 的逐 patch 反馈齐备
补丁目的
解除 CONFIG_SCHED_PROXY_EXEC 与 CONFIG_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 限制打开同编译。
旧流程的问题
SCHED_PROXY_EXEC depends on !SCHED_CLASS_EXT——内核必须挑一个。set_task_cpu()对is_migration_disabled(p)无条件 WARN,proxy 搬调度上下文(不搬执行上下文)触发假正例。sched_can_stop_tick()看rq->curr+ 要求nr_running == 1;proxy 下受限 FAIR donor 漏算 tick 可能停掉。consume_remote_task()用 holding_cpu 握手防迁移;proxy 期间 owner 已上线但 holding_cpu 未清,导致 sleeping-while-atomic + lockdep corruption。ops.running()对一个根本不会跑的 blocked donor 仍然触发,紧随的ops.stopping()仍会调,形成不配对。- 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:fixconsume_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_blockedkselftest。 - 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_TRACKED、SCX_TASK_ENQ_WAKEUP两个 task flag 严格配对 running/stopping 与 wakeup 跟踪。 - 引入 ops flag
SCX_OPS_ENQ_BLOCKED与 enq flagSCX_ENQ_BLOCKED,让 BPF 显式表态接收 blocked donor。 task_can_move_from_locked_rq()在 PATCH 06 引入、在 PATCH 08 由 active-donor 检查补强。- 把散落的 inline block 逻辑收敛到
sched_proxy_block_task()助手。 - 新增
enq_blockedkselftest 取代早期手工验证。 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 误判。