0/1 已展开

LLM 分析

核心调度器:__sched_core_flippick_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.ckernel/sched/sched.h
  • 修改文件: kernel/sched/core.ckernel/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.ygit 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 选任务跑到错误锁语义上,导致锁错乱与潜在跨线程侧信道。