sched-ext discussion
[PATCHSET v13 sched_ext/for-7.4] sched: Make proxy execution compatible with sched_ext
LLM 分析
sched:让 proxy execution 与 sched_ext 兼容(v13)
系列概况
- 标题: sched: Make proxy execution compatible with sched_ext
- 作者: Andrea Righi arighi@nvidia.com
- 版本: PATCHSET v13,目标分支
sched_ext/for-7.4 - 规模: 18 个补丁
- 修改文件:
kernel/sched/core.c、kernel/sched/fair.c、kernel/sched/sched.h、kernel/sched/syscalls.c、kernel/sched/ext/{ext.c,ext.h,sub.c,sub.h,internal.h}、include/linux/sched/ext.h、init/Kconfig、Documentation/scheduler/sched-ext.rst、tools/sched_ext/scx_qmap.{bpf.c,c,h}、tools/testing/selftests/sched_ext/{Makefile,config,.gitignore,enq_blocked.bpf.c,enq_blocked.c,test_modules/*} - 代码统计: 核心代码 +260/-130 左右;新增 ~917 行 kselftest C 代码 + 116 行 BPF 程序 + ~50 行 qmap 改动
- Message-ID: 20260831134338.1531664-1-arighi@nvidia.com
- 完整性: 完整(18/18 patch + cover letter + 7 条回复)
补丁目的
解开 CONFIG_SCHED_PROXY_EXEC=y 与 CONFIG_SCHED_CLASS_EXT=y 的 Kconfig 互斥,并新增一个 per-scheduler 开关 SCX_OPS_ENQ_BLOCKED,让 BPF 调度器能够显式接管被 mutex 阻塞、但作为 donor 仍留在 runqueue 上的任务。一次性解决"发行版/运行时特性只能二选一"以及"BPF helper/kfunc 看到的 current 与内核实际执行上下文不一致"两个老问题。
旧流程的问题
- Kconfig
SCHED_PROXY_EXEC depends on !SCHED_CLASS_EXT:两个特性无法同时编译。 __schedule()用!task_is_blocked(prev)把所有 blocked task 摘出 rq,proxy-exec 路径走不到。rq->curr在 proxy 下指 lock owner,而scx_bpf_task_running/cpu_curr/cid_curr都把rq->curr当成"BPF 选中的任务",kfunc 视角与实际不一致。set_task_cpu()对 migration-disabled 任务无条件 WARN_ON_ONCE,proxy 把 blocked donor 调度上下文搬到 owner CPU 时会假阳性。sched_can_stop_tick()仅在nr_running==1时检查 CFS bandwidth,被 retained 的 FAIR donor 会漏掉带宽管控。dequeue_task_scx用task_current(rq, p)判断是否发stopping,proxy 下"调度上下文切换 ≠ 物理切换",导致 running/stopping 配对错乱。
新流程
- core 在
sched_change_begin()中传入 next_class,新增sched_proxy_block_task(),所有"调度类/BPF 所有权变更"先全量 dequeue 残留 donor; __schedule()改为只在task_is_blocked(prev) && scx_allow_proxy_exec(prev)时保留 prev 为 donor;新增WF_ON_RQ区分 ttwu_runnable 与全新 wakeup;rq->donor成为 BPF 视角的"current",rq->curr是实际执行上下文;SCX_TASK_RUN_TRACKED 显式跟踪 running session;- 跨 rq DSQ 转移遇到 proxy 状态时,把任务 park 到 source rq 的
reject_dsq,并标记SCX_TASK_REENQ_PROXY,等scx_proxy_resolved()后再让 BPF 重决策; - BPF 调度器可设置
SCX_OPS_ENQ_BLOCKED接管 donor admission,core 透传SCX_ENQ_BLOCKED给ops.enqueue(); - 取消 Kconfig 互斥。
Patch 概览
| # | 类型 | 主题 |
|---|---|---|
| P01 | cleanup | find_proxy_task() 用统一 label 在 mutex 解锁后再 resched |
| P02 | refactor | block_task() 拆 dequeue_block_task(),让 donor 先 dequeue 再 reset |
| P03 | bugfix | sched_can_stop_tick() 改用 rq->donor 检查带宽,Fixes: af0c8b2bf67b |
| P04 | bugfix | set_task_cpu() 对 blocked proxy donor 关闭 WARN_ON_ONCE |
| P05 | refactor | sched_change_begin() 增加 next_class 参数 |
| P06 | feature | 新增 sched_proxy_block_task() 辅助函数 |
| P07 | feature | 新增 3 个空 hook:scx_allow_proxy_exec/proxy_donor_start/proxy_resolved |
| P08 | feature | 新增 WF_ON_RQ wake flag |
| P09 | feature | scx_allow_proxy_exec() 默认拒绝 EXT donor;ownership change 时强制 deactivate |
| P10 | bugfix | 引入 SCX_TASK_RUN_TRACKED,修正 running/stopping 配对 |
| P11 | refactor | reject DSQ 从 sub.c 移到 ext.c 主代码 |
| P12 | refactor | 通用化 reject DSQ,新增 reenq reason 通道 |
| P13 | bugfix | 远端 DSQ 转移与 proxy 竞态:把任务 park 在 source rq |
| P14 | refactor | 把 ext.c 内 rq->curr 引用统一替换为 rq->donor |
| P15 | feature | 新增 SCX_OPS_ENQ_BLOCKED、SCX_ENQ_BLOCKED;BPF 接管 donor admission |
| P16 | test | 新增 kselftest enq_blocked(同 CPU/跨 CPU × 开关) |
| P17 | feature | scx_qmap 新增 -X 选项启用 proxy-exec |
| P18 | config | 移除 SCHED_PROXY_EXEC depends on !SCHED_CLASS_EXT |
关键实现
1. donor / owner 解耦
/* rq 维护两套指针 */
rq->donor /* BPF 视角的 scheduling context */
rq->curr /* 实际执行 context (lock owner) */
/* set_next / put_prev 改走 donor */
donor->sched_class->put_prev_task(rq, donor, donor);
donor->sched_class->set_next_task(rq, donor, true);
2. 仅在 BPF 同意时保留 donor
/* __schedule() */
!task_is_blocked(prev) || !scx_allow_proxy_exec(prev))
/* 默认拒绝 EXT */
bool scx_allow_proxy_exec(const struct task_struct *p) {
if (p->sched_class != &ext_sched_class)
return true;
sch = scx_task_sched(p);
return !sch || (sch->ops.flags & SCX_OPS_ENQ_BLOCKED);
}
3. running session 跟踪
/* 仅当真的换了 donor 才发 stopping */
if (next != p && (p->scx.flags & SCX_TASK_QUEUED) &&
(p->scx.flags & SCX_TASK_RUN_TRACKED)) {
SCX_CALL_OP_TASK(sch, stopping, rq, p, true);
p->scx.flags &= ~SCX_TASK_RUN_TRACKED;
}
/* proxy 迁移后, 在新 CPU 等 scx_proxy_donor_start() 才开 session */
if (donor->sched_class == &ext_sched_class &&
(donor->scx.flags & SCX_TASK_QUEUED))
scx_start_task_running(rq, donor);
4. 远端 DSQ 转移遇到 proxy
if (unlikely(task_proxy_unsafe_to_move(p))) {
p->scx.dsq = NULL;
scx_proxy_reject_task(sch, src_rq, p, enq_flags | SCX_ENQ_CLEAR_OPSS);
switch_rq_lock(src_rq, this_rq);
return false;
}
5. BPF 接管 donor admission
/* core 透传 */
if ((sch->ops.flags & SCX_OPS_ENQ_BLOCKED) && p->is_blocked &&
!(enq_flags & SCX_ENQ_WAKEUP))
enq_flags |= SCX_ENQ_BLOCKED;
类比
把 proxy execution 想成餐厅里"等候者把点菜单让给已经被服务的桌":donor 是还在等候区排队、已经决定要点什么菜的顾客,owner 是已经坐下但被厨房卡住的桌。当 owner 卡住时,donor 的"调度上下文(菜单决策权)"临时借给 owner,让 owner 那桌先上菜;但服务员(BPF)始终只认 donor 是下单人,账也算在 donor 头上。只有当 donor 同意(BPF 设置 SCX_OPS_ENQ_BLOCKED)时,这种"借菜单"才被允许;如果 donor 没同意(默认),就老老实实回去排队,owner 也回到正常阻塞。
Highlight:风险与注意点
- Kfunc 视角错乱风险:所有依赖
rq->curr的 BPF helper 必须改为rq->donor,P14 一次性改完,但 BPF 程序侧(用户态)如果自己缓存 current 指针就需要重读; - running/stopping 嵌套:忘记
SCX_TASK_RUN_TRACKED会让 BPF 收到重复的 running/stopping,影响 vtime/slice 统计; - tick 带宽检查漏检:P03 之前 proxy-exec 下 nr_running>1 直接跳过带宽检查,长时间运行可能让受限 FAIR donor 抢不到 CPU;
- 远端 DSQ 转移竞态:必须在拿到 source rq 锁后重新检查
task_proxy_running_or_donating(),否则可能把还在执行的 task 拉走; - 保守策略遗留:RT/DL PI 链路上保留 proxy session 的能力被推迟,仍可能存在不必要的 wakeup/queue;
- scx_qmap 政策激进:默认行为对争用 mutex 的任务非常偏袒,仅适合 demo,生产调度器应基于此自行设计策略。
版本变化
- v1–v3(2026-05–2026-06):基础拆分,把 proxy 与 sched_ext 改动集中放在 sched/core 与 ext.c 早期版本;
- v4–v7(2026-07 上旬):开始拆分
sched_proxy_block_task(),并把"调度类切换前 deactivate"显式化; - v8–v10(2026-07 中下旬):引入
SCX_TASK_RUN_TRACKED,把 ops.running/stopping 从task_current改为 donor-aware; - v11–v12(2026-08 中旬):reject DSQ 从 sub-sched 专用改为通用,新增
SCX_TASK_REENQ_PROXY; - v13(2026-08-31):scx_qmap 从默认开启 proxy 改为
-Xopt-in;kselftest 加入同 CPU / 跨 CPU × 开关矩阵。
一句话总结
v13 通过 SCX_OPS_ENQ_BLOCKED 让 BPF 调度器显式接管被 mutex 阻塞的 donor,并把 sched_ext 的"current"视角从物理 rq->curr 改成逻辑 rq->donor,最终在 Kconfig 上解除了 proxy-exec 与 sched_ext 的互斥。
+----------------------+
| BPF scheduler |
| ops.enqueue() |
| SCX_ENQ_BLOCKED? |
+----------+-----------+
| admit donor
v
+-----------------------------------+
| sched core: __schedule() |
| task_is_blocked(prev) && |
| scx_allow_proxy_exec(prev) |
+-----------------+-----------------+
| yes
v
+-----------------------------------+
| rq->donor = blocked donor |
| rq->curr = mutex owner |
+-----------------+-----------------+
|
v
+-----------------------------------+
| find_proxy_task(): migrate donor |
| scheduling context -> owner CPU |
+-----------------+-----------------+
|
v
+-----------------------------------+
| SCX_TASK_RUN_TRACKED session |
| - open : scx_proxy_donor_start |
| - close : donor really changes |
+-----------------------------------+
remote DSQ xfer races with proxy ?
|
v
+----------------------------+
| task_proxy_unsafe_to_move |
| - task_on_cpu(src_rq,p) |
| - task_current_donor() |
+-------------+--------------+
| unsafe
v
+----------------------------+
| park on src_rq.reject_dsq |
| SCX_TASK_REENQ_PROXY |
| wait scx_proxy_resolved() |
+----------------------------+