sched discussion
FAILED: patch "[PATCH] sched/core: Make core-sched flips wait for in-flight" failed to apply to 5.15-stable tree
LLM 分析
核心调度器:__sched_core_flip 与 pick_next_task 竞态修复
系列概况
- 标题: FAILED: patch "[PATCH] sched/core: Make core-sched flips wait for in-flight" failed to apply to 5.15-stable tree
- 作者: Greg Kroah-Hartman (gregkh,stable 树维护者;上游 patch 的实际作者是 Tejun Heo tj@kernel.org)
- 版本: 单封邮件(stable 树自动回信),引用上游 commit
f3629c63a4af3e491381780bc6c123cb498c4c40 - 规模: 一封邮件,承载一个小型补丁;diff 涉及
kernel/sched/core.c与kernel/sched/sched.h - 修改文件:
kernel/sched/core.c、kernel/sched/sched.h(基于邮件正文给出的 diff 头与片段) - 代码统计: 邮件正文内可见的核心 diff 总计大约 +28 行,包含新增
core_pick_in_flight字段及其 4 处使用点 - Message-ID:
2026090312-earlobe-rendering-b0fe@gregkh - 完整性: 邮件结构完整,包含 stable 通知前缀、cherry-pick 指引、原 commit 信息(subject/author/date/body/Fixes/Cc stable/Sob/Acked-by)以及 commit hash
补丁目的
核心调度(CONFIG_SCHED_CORE,让同 SMT 兄弟线程不会跨越受信边界共享数据)在 pick_next_task() 阶段用一个跨兄弟核的 "core-wide 共享锁" 串行选中下一个任务。某些调度类(带有 ->pick_task() 钩子)为了避免优先级反转,会在该钩子里临时放掉 rq 锁再重新拿回。
这放出了一个窗口:原共享锁此刻被切分成多个独立的兄弟锁,外部 __sched_core_flip(false)(关闭 core-sched)若正好发生,就会把 rq_lockp() 重新指回普通的 per-rq 锁实现,而 pick_next_task 还在原路径上跑,继续拿着已经不再属于它的 "共享概念" 去访问兄弟核状态。最后 __schedule() 退出时还会释放一把从未真正拿过的锁、漏放一把实际持有的锁,属于典型的锁语义错乱,攻击者甚至能借此构造跨线程侧信道。
补丁目标:把 "我正在进行 core-wide 选任务" 这件事显式计数,并让 __sched_core_flip() 在翻面前等到所有进行中的选任务逻辑彻底排干。
旧流程的问题
flip(false) +--> __schedule() releases "shared lock"
| | (but it only held per-rq locks)
pick_next_task holds "shared lock"
+--> --> ->pick_task() --> drop rq lock --> re-acquire --> ...
| | |
| +-- at this moment __sched_core_flip
| rebinds rq_lockp() to per-rq
|
+---- selection resumes on split locks
touching sibling state it no longer protects
核心问题:缺少 "我正握着 core-wide 锁" 的可见标志,flip 没有拒绝窗口内翻面。
新流程
新增字段 rq->core_pick_in_flight(32 位计数器),且只在 leader rq 上有意义:
leader->core_pick_in_flight
|
pick_next_task in | +1
|
__sched_core_flip() ----> while (leader->core_pick_in_flight) {
| sched_core_unlock(cpu, &flags);
| cpu_relax();
| sched_core_lock(cpu, &flags);
| }
|
pick_next_task out | -1
sched_core_cpu_deactivate() 时不是赋值拷贝,而是 move:旧 leader 的计数搬到新 leader,旧位置清零,避免这块 CPU 之后回插成为 leader 时把陈旧计数带回去造成永久偏差。
关键实现
以下是核心翻转部分(基于邮件正文里 hunk 的代码语义重建):
static void __sched_core_flip(bool enabled)
{
struct rq *rq;
unsigned long flags;
int cpu;
sched_core_lock(cpu, &flags);
/*
* A core-wide selection may have the shared rq lock temporarily
* released by a lock-dropping ->pick_task(). Flipping would
* rebind rq_lockp() under it. Wait it out.
*/
while (cpu_rq(cpu)->core->core_pick_in_flight) {
sched_core_unlock(cpu, &flags);
cpu_relax();
sched_core_lock(cpu, &flags);
}
/* ... smt_mask 上每个 CPU 的 core_enabled = enabled ... */
}
与之配对的进出计数:
pick_next_task(struct rq *rq, struct rq_flags *rf)
{
/* ... 入口 */
rq->core->core_pick_in_flight++;
/* ... 真正选任务 ... */
rq->core->core_pick_in_flight--;
return __pick_next_task(rq, rf);
}
deactivate 时的 move:
static void sched_core_cpu_deactivate(unsigned int cpu)
{
/* ... A stale leftover would bias the count forever if this CPU
* later returns as its own leader. Move, don't copy. */
core_rq->core_pick_in_flight = rq->core->core_pick_in_flight;
rq->core->core_pick_in_flight = 0;
}
几个不变量必须保持:
- 计数器只在 core-wide 共享锁下改动;
__sched_core_flip()抽样的同一把共享锁负责读,因此不需要额外的 ordering。 - 重试循环里先 unlock、再 lock,是为了避免在 lock-dropping
->pick_task()持锁时死锁。 - 翻转是 cookie 生命周期内极少发生的事件,busy-wait 是可接受的。
- leader 切换时显式 move,旧 leader 槽位归零,避免无主的 +N 永远遗留。
类比
把 SMT 兄弟核想成金库的两扇并排保险柜门,它们共用一把电子总控钥匙。pick_next_task() 是客户经理进出金库:
- 平时一气呵成开门办业务。
- 可某些 VIP 客户(lock-dropping
->pick_task())办业务前需要换工具,因此经理会临时把总控钥匙放回卡座,自己用单独的开柜工具分别打开两个柜子继续工作。 - 若此时 IT 部门发指令 "换钥匙系统"(
__sched_core_flip)并立即生效,经理回头再试总控钥匙时,钥匙形状已变,柜门状态也不再受保护。
补丁相当于在卡座边新增一块小屏幕:「正在使用中」。IT 必须等屏幕显示 0 才可以换钥匙。换主管柜员(leader)时还要把屏幕上的数字亲手搬过去,而不是拍照(move 而非 copy),否则下一个上任的主管会被旧数据误导。
Highlight:风险与注意点
- 回归点:
Fixes: 539f65125d20 ("sched: Add core wide task selection and scheduling"),自 v5.14 起都受影响;stable tree 才被Cc到。 - 未来扩展风险:现在的
core_pick_in_flight只在 leader rq 上有意义;将来若引入更复杂的非对称拓扑(不同 SMT 域大小、或嵌套 leader),需要重新审视 "leader-only" 假设。 - deadlock 隐患:等待循环一定要在 unlock 状态下
cpu_relax();如果误在持锁时 sleep,可能与同样在等这把锁的->pick_task()路径互锁,上游已用sched_core_unlock+cpu_relax+sched_core_lock规避,移植到旧版时必须保留这种结构。 - copy vs move:deactivate 用 move 是关键;如果直接
rq->core->core_pick_in_flight = new->...复制,是经典的 "陈旧 cache" 型 bug,CPU 回插后会持续看到自己之前留下的计数。 - stable 回信机制:邮件给的是 cherry-pick 流程(
git fetch linux-5.15.y、git cherry-pick -x f3629c63...),谁愿意维护 5.15 这条线谁去提交,不必 Greg 单独去做。这是 Greg 自动化回信的固定模板。 - 评分:该修复涉及跨核锁语义与侧信道,是 core-sched 自引入以来少数 "必须修" 的正确性问题。
版本变化
仅有 v1(原 commit f3629c63a4af,由 Tejun Heo 在 2026-08-07 提交,已带 Acked-by: Peter Zijlstra),并且本邮件是一次 stable 树自动化的回信,没有新的 v2。如果任何人 backport 到 5.15 后产生冲突,可能衍生出 PATCH 5.15.y 的小修订,但那不在本线程中。
一句话总结
__sched_core_flip 翻面需等待 leader 上 core_pick_in_flight 归零;否则在 ->pick_task() 放锁窗口里把共享锁切换回 per-rq 锁,会让正在进行的 core-wide 选任务跑到错误锁语义上,导致锁错乱与潜在跨线程侧信道。