sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Update core-sched task ordering for scheduler hierarchies
LLM 分析
sched_ext:core-sched 任务排序对 scheduler 层级结构的对齐修正
系列概况
- 标题:
[PATCHSET sched_ext/for-7.3] sched_ext: Update core-sched task ordering for scheduler hierarchies - 作者:Tejun Heo tj@kernel.org
- 版本:基于 sched_ext/for-7.3(commit 3167bd3e0c22),未声明 vN,单 PATCHSET 直接投递
- 规模:4 个 patch,覆盖 4 个文件,+81 / -97 行
- 修改文件:
include/linux/sched/ext.h、kernel/sched/ext/ext.c、kernel/sched/ext/internal.h、tools/sched_ext/scx_qmap.bpf.c - Message-ID:20260816000527.988170-1..5-tj@kernel.org;落盘前由作者本人在 329d3d9a... 的回复中 Applied 1-4
- 完整性:完整 4 封 patch + 1 封 maintainer applied 公告,共 6 封
补丁目的
sub-schedulers 引入之后,core-sched 在做 SMT 兄弟核任务排序时仍沿用 sub-scheduler 出现之前的规则。这套规则存在两个互不兼容的缺陷:
ops.core_sched_before()被scx_prio_less()反向调用,导致返回值语义颠倒;- 默认 fallback 用
p->scx.core_sched_at,但这个时间戳在「放入等待」和「dispatch」两种路径下用两套规则维护,比较结果不构成有意义的序。
本系列一次性修掉三处错误并清理一段死代码,目标是在 merge window 打开前把 sub-scheduler 路径的核心坑补齐。
旧流程的问题
scx_prio_less()返回a排在b之后,但ops.core_sched_before()的文档约定是a排在b之前,scx_prio_less()把 op 结果原样返回等于在运行时把语义反转;scx_qmap反向跟着调偏,把两次反转相抵消才恰好工作。core_sched_at在「等待开始」与「dispatch」两种路径下各用一套刷新规则:同一对比较对象可能一个时间戳来自 dispatch、另一个来自等待路径,根本不在同一时基上秤重。scx_prio_less()只在sch_a == sch_b且 op 存在时调用 op,跨 scheduler 都退化到默认序,根 scheduler 无法对子 scheduler 的任务表达顺序。dequeue_task_scx()里有deq_flags & SCX_DEQ_CORE_SCHED_EXEC测试,但SCX_DEQ_CORE_SCHED_EXEC是 bit 32,而deq_flags是 int(高 32 位被截掉),且 core-sched execute 路径根本不走 class dequeue,分支永远为假。
新流程
- 调用 op 时交换参数
(a, b)改为(b, a),让两边语义一致;scx_qmap同步翻转比较方向。 - 删掉
core_sched_at与touch_core_sched*(),fallback 直接复用p->scx.runnable_at(stall watchdog 已经在维护的等待时间)。在scx_prio_less()中显式把on_cpu的任务排在所有等待任务之后,把「刚被服务过」这个事实表达出来。 - 跨 scheduler 的配对,沿着
ancestors[]找最近的公共祖先;只有这个祖先实现了core_sched_before()才调用它的 op,且仅在该祖先对两个 CPU 都不 bypass 时调用。 - 删除
dequeue_task_scx()中那段永远为假的SCX_DEQ_CORE_SCHED_EXECmask。
Patch 概览
| # | 主题 | 性质 |
|---|---|---|
| 1/4 | Fix inverted ops.core_sched_before() invocation | bugfix |
| 2/4 | Use runnable_at for the default core-sched task ordering | cleanup+bugfix |
| 3/4 | Make core-sched task ordering hierarchy-aware | feature/enable |
| 4/4 | Drop the dead SCX_DEQ_CORE_SCHED_EXEC test | cleanup |
关键实现
/* patch 1: swap op arguments, remove runtime polarity inversion */
return SCX_CALL_OP_2TASKS_RET(sch_a, core_sched_before,
task_rq(a),
(struct task_struct *)b,
(struct task_struct *)a);
/* scx_qmap flips its comparator to match the documentation */
return task_qdist(a) < task_qdist(b);
/* patch 2: use runnable_at instead of core_sched_at;
* running task is ordered after every waiting task */
bool a_running = a->on_cpu;
bool b_running = b->on_cpu;
if (a_running != b_running)
return a_running; /* a on_cpu => a runs after b */
return time_after(a->scx.runnable_at, b->scx.runnable_at);
/* patch 3: walk ancestors[] to pick the nearest common arbiter */
struct scx_sched *sch = NULL;
if (sch_a == sch_b) {
if (SCX_HAS_OP(sch_a, core_sched_before))
sch = sch_a;
} else {
for (level = min(sch_a->level, sch_b->level); level >= 0; level--) {
struct scx_sched *anc = sch_a->ancestors[level];
if (anc == sch_b->ancestors[level] &&
SCX_HAS_OP(anc, core_sched_before)) {
sch = anc;
break;
}
}
}
if (sch && !scx_bypassing(sch, task_cpu(a)) && !scx_bypassing(sch, task_cpu(b)))
return SCX_CALL_OP_2TASKS_RET(sch, core_sched_before, ...);
/* patch 4: dead mask test removed */
if (!(deq_flags & DEQUEUE_SLEEP))
类比
想象一座办公楼里有多部电梯:每部电梯(scheduler)自己排乘客顺序,各自的科长只看得到自己楼层的人。SMT 兄弟核就像同一楼层的两部电梯——必须先确定哪部先走、谁先上。
- patch 1:之前调度员把「先下后上」规则的真假表贴反了,电梯按钮却又恰好按反方向,两错相消才看起来工作;现在把按钮贴正。
- patch 2:以前每个乘客领一张「开始排队时间」小票,但大楼有两个发票口(等待窗口、发车窗口),两个口子的钟不一样。改成统一用「开始排队时间」一张票(runnable_at),并且规定:已经在电梯里(on_cpu)的人总是最晚被服务。
- patch 3:以前每部电梯只懂自己楼层的人,跨楼层协调就乱套。现在会议室主管(最近共同祖先)出来当裁判,只有他下达的指令才能跨楼层生效。
- patch 4:电梯调度代码里有一段「如果乘客从 32 楼来就不让他进」的死测试,32 楼压根没人坐这部电梯,删掉。
+------------------+ +------------------+ +------------------+
| scx_prio_less |----->| core_sched_before|---->| real order |
| "a runs after b" | | (b, a) | | |
+------------------+ +------------------+ +------------------+
cross-scheduler pair (patch 3)
+-----------+ +-----------+
| sch_a | | sch_b |
| level=2 | | level=1 |
+-----+-----+ +-----+-----+
| \ /
| \ walk ancestors[] /
v v v
+-----+----+------------------+-----+
| nearest common ancestor |
| that implements core_sched_before |
+------------------------------------+
|
v
op called on the arbiter
(old) core_sched_at (new) runnable_at + on_cpu
+---------------------+ +-----------------------+
| touch on wait start | | refreshed on enqueue |
| touch on dispatch | ====> | running task ordered |
| two rules mixed | | after every waiter |
+---------------------+ +-----------------------+
Highlight:风险与注意点
core_sched_at字段被整个删掉,include/linux/sched/ext.h 之外若有 out-of-tree 用户依赖该字段,会编译失败;Cc stable 是 v6.12+,意味着回稳分支也需要带上前两个 patch。scx_qmap的翻转方向是「与其他可能的 out-of-tree 使用者」配对推断的;任何把 BPF scheduler 写成return task_qdist(a) > task_qdist(b)的下游,都需要同步翻为<,否则回到「看起来对、实际反」的状态。runnable_at只在 enqueue 时刷新,长跑任务会带陈旧戳;当前用on_cpu兜底,但若将来出现 running 但不 on_cpu(如被 rq 临时拔出)状态需要再确认。- 跨 scheduler 走「最近共同祖先」意味着祖先实现了 op 才会调用;未实现任何 core_sched_before 的根 scheduler 仍退回默认序,行为是「等待更久者优先」,这是显式边界,不是 bug。
- 第 4 个 patch 的清理依赖 03f5304aad0f 的修补历史;如果哪个回稳分支没有那个 commit,这段「死代码」其实是活代码,cherry-pick 时要小心。
一句话总结
把 scx_prio_less() 的 op 反向调用与 core_sched_at 两套时间戳规则修掉,让跨 sub-scheduler 的任务序交给「最近共同祖先」仲裁,并顺手清掉一段死测试,在 7.3 merge window 前把 sub-scheduler 的核心坑补齐。