sched-ext discussion
[PATCHSET v11 sched_ext/for-7.3] sched: Make proxy execution compatible with sched_ext
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.h、init/Kconfig、Documentation/scheduler/sched-ext.rst、tools/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-ID:
20260810151523.86994-1-arighi@nvidia.com至-16 - 完整性:完整,cover letter 含背景、设计、示例输出,附新 kselftest 与 qmap 演示
补丁目的
解除 CONFIG_SCHED_PROXY_EXEC 与 CONFIG_SCHED_CLASS_EXT 的构建时互斥,让发行版和终端用户能在同一内核中按需组合特性。核心思路:把"调度上下文(donor)"和"执行上下文(lock owner)"在 sched_ext 视图下分离,由 BPF 通过新 ops flag SCX_OPS_ENQ_BLOCKED 决定是否参与。
旧流程的问题
- 构建期强制互斥:
depends on !SCHED_CLASS_EXT,发行版只能二选一。 - 任务视角不一致:sched_ext 假设
rq->curr就是被调度任务;proxy 把 rq->curr 换成 owner,BPF 视角会"看到另一个 task"。 - 回调配对错位:proxy 同 CPU 切换 owner 时,会触发多余的 running/stopping 事件。
- NOHZ 带宽检查漏洞:
__need_bw_check()限定rq->nr_running == 1,被 throttle 的 FAIR donor 会漏掉 enforcement。 set_task_cpu误报:proxy 把 donor 调度上下文搬到 owner CPU 时,is_migration_disabled检查会触发 WARN。- 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_PROXY与task_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_blockedselftest(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_EXEC 与 SCHED_CLASS_EXT 的构建时互斥。