0/8 已展开

LLM 分析

sched_ext: 修复 idle CPU 状态初始化与验证

系列基线信息

字段内容
标题[PATCHSET v2 sched_ext/for-7.3] sched_ext: Fix idle CPU state initialization and validation
作者Andrea Righi (NVIDIA)
版本v2(目标分支 sched_ext/for-7.3
规模2 个 patch,3 个文件,+75 / -10
Message-ID20260731090334.2911948-1-arighi@nvidia.com
来源sched-ext 邮件列表

明确目的

sched_ext 的内置 idle mask 在初始化时将所有 online CPU 标记为 idle,但 idle 状态追踪直到调度器完全启用后才启动。

这导致两个问题:

  1. 核心 bugops.init() 执行时以及此后直到 CPU 首次进入 idle 之前,busy CPU 会被错误地报告为 idle。
  2. 自测竞态allowed_cpus 自测验证远程选中的 CPU 不再在 idle mask 中,但 idle-to-idle 重选可能在验证前重新将该 CPU 标记为 idle,导致验证本身是竞态的。

本系列修复这两个问题:在 ops.init() 之前就启用 idle 追踪并同步每个 online CPU 的状态;同时将自测从远程 CPU 检查改为稳定的本地不变量。


遍历代码

Patch 1/2:核心修复 — kernel/sched/ext/idle.c + ext.h

新增静态键 scx_idle_tracking_enabled

DEFINE_STATIC_KEY_FALSE(scx_idle_tracking_enabled);  // idle.c
DECLARE_STATIC_KEY_FALSE(scx_idle_tracking_enabled);  // ext.h

__scx_update_idle() 中的入口条件从 scx_enabled() 改为 static_branch_unlikely(&scx_idle_tracking_enabled),使 idle 追踪可以在调度器完全启用前就开始工作。

__scx_update_idle() 中抑制 ops.update_idle() 回调

if (!scx_enabled())
    return;  // 只更新内置 mask,不通知 BPF 程序

这确保了早期阶段只更新内置 idle mask,BPF 侧的 ops.update_idle() 在调度器完全启用前不会被触发。

新增 refresh_idle_masks() 函数

static void refresh_idle_masks(void)
{
    int cpu;
    for_each_online_cpu(cpu) {
        struct rq *rq = cpu_rq(cpu);
        scoped_guard(rq_lock_irqsave, rq)
            update_builtin_idle(cpu, rq->curr == rq->idle);
    }
}

逐个 CPU 持 rq lock 读取当前运行任务,判断是否为 idle task,更新内置 idle mask。一旦刷新完成,后续的 idle 转换会持续保持状态准确。

scx_idle_enable() 调用链

  1. static_branch_enable_cpuslocked(&scx_idle_tracking_enabled) — 启用追踪
  2. scx_builtin_idle_enabled 为真,调用 refresh_idle_masks() — 同步初始状态
  3. reset_idle_masks() 的注释更新:从"Should converge quickly"改为"Seed all online CPUs as idle. refresh_idle_masks() below corrects their state before ops.init() runs."

scx_idle_disable() 中关闭静态键static_branch_disable(&scx_idle_tracking_enabled)

Patch 2/2:自测修复 — allowed_cpus.bpf.c

删除 validate_idle_cpu()(检查远程选中 CPU 不在 idle mask 中 — 竞态)。

新增 validate_local_idle_state()

static void validate_local_idle_state(void)
{
    s32 cpu = bpf_get_smp_processor_id();
    bool curr_is_idle;

    bpf_rcu_read_lock();
    curr = scx_bpf_cpu_curr(cpu);
    curr_is_idle = curr && (curr->flags & PF_IDLE);
    bpf_rcu_read_unlock();

    if (!curr_is_idle && scx_bpf_test_and_clear_cpu_idle(cpu))
        scx_bpf_error("running CPU %d should be marked as busy", cpu);
}

关键不变量:本地 CPU 正在运行非 idle 上下文,则不能被标记为 idle。本地 CPU 不可能在回调执行期间经历 idle 重选,因此这个检查是稳定的。

新增 validate_selected_cpu():验证选中 CPU 在 allowed domain 和 task affinity 内。

调用点ops.select_cpu()ops.enqueue() 中用 validate_local_idle_state() + validate_selected_cpu() 替换原来的 validate_idle_cpu()


ASCII 流程图

                 启用 sched_ext 时的时序
                 ========================

  scx_idle_enable()
        |
        v
  [1] enable scx_idle_tracking_enabled
        |
        v
  [2] refresh_idle_masks()
        |   for_each_online_cpu(cpu):
        |     rq_lock(cpu)
        |     if (rq->curr == rq->idle)  -->  idle mask: set bit
        |     else                        -->  idle mask: clear bit
        |     rq_unlock(cpu)
        |
        v
  [3] reset_idle_masks()  --  seed all online CPUs as idle
        |                     (refresh 已经纠正了真实状态)
        v
  [4] ops.init()  <--  此时 idle mask 已准确
        |
        v
  [5] scx_enabled() == true
        |   __scx_update_idle() 现在也触发 ops.update_idle()
        v
  正常运行

  =====================================

  自测不变量对比 (旧 vs 新)

  旧:  远程 CPU 被选中后,检查它不在 idle mask
       [select_cpu] --> pick remote CPU X
                         |
                   [idle re-pick] --> CPU X re-advertised as idle!
                         |
                   [validation]  --> FAIL (竞态)

  新:  本地 CPU 运行非 idle 上下文时,不能被标记为 idle
       [select_cpu on local CPU]
         curr == idle?  NO
         idle mask bit set?  YES --> scx_bpf_error
         (本地 CPU 不可能被重选,检查稳定)

概念类比

想象一个酒店前台系统:

  • 旧方案:酒店开业时,系统默认所有房间都是"空闲"状态。但实际有些房间已经有客人入住了。只有当客人退房或入住时,系统才会更新房间状态。这导致前台在营业初期可能把已有客人的房间分配给新客人。

  • 新方案:酒店开业前,系统先派管理员逐个房间检查(refresh_idle_masks()),确认每个房间真实状态后才开放预订。同时,在正式营业前,只更新内部状态面板,不通知外部系统(ops.update_idle() 抑制)。营业后,状态面板和外部通知同步更新。

自测的类比:旧方案是"分配了房间后,再回头确认房间确实被标记为已占用"——但客人可能已经退房了(idle 重选)。新方案是"站在前台的人如果正在服务客人,房间状态就不该显示空闲"——这是本地可观察的、不会被打断的事实。


Highlight 突出问题

  1. v3 将合并静态键:Kuba 指出 scx_idle_tracking_enabledscx_builtin_idle_enabled 可合并为一个。Andrea 已同意在 v3 中重做,用 scx_builtin_idle_enabled 做早期追踪,保留 scx_enabled() 检查来抑制 ops.update_idle() 回调。如果用户自行做 idle 追踪,内置 idle 被禁用,SCX 不需要追踪 idle 状态。

  2. idle mask 反方向不变量不成立:Kuba 建议也检查"运行 idle task 时 idle bit 必须为 set"。Andrea 指出反方向不成立——BPF idle 选择 helper 可以在 CPU 仍在运行 idle task 时清除 idle bit(被选中),这是合法的。验证时不能假设"idle task → idle bit set"。

  3. validate_local_idle_state() 是否应扩展到 ops.enqueue():Kuba 提出该不变量不仅适用于 ops.select_cpu(),也应在 ops.enqueue() 中检查。Andrea 的回复中未明确反对,v3 可能扩展。

  4. refresh_idle_masks() 的 CPU 热插拔窗口:函数遍历 for_each_online_cpu() 并持 rq lock,但如果在遍历期间有 CPU 上线/下线,需要确认在线 CPU 集合的稳定性。代码注释声称"online CPU set is stable",但依赖调用上下文保证(scx_idle_enable()cpus_read_lock 下调用)。


版本演进

版本关键改动
v1idle mask 初始化放在 selftest 中;直接使用 scx_enabled() 作为追踪开关
v2将 idle mask 初始化移入 sched_ext 核心(Kuba 建议);新增 scx_idle_tracking_enabled 静态键在调度器启用前开启追踪;重写 allowed_cpus 自测为本地不变量验证;目标分支从 for-7.2-fixes 改为 for-7.3
v3(待发布)合并 scx_idle_tracking_enabledscx_builtin_idle_enabled(Kuba + Andrea 共识);可能扩展不变量检查到 ops.enqueue()

与其他相关 patch 系列的关联

  • v1 链接https://lore.kernel.org/all/20260726064754.378671-1-arighi@nvidia.com/,这是同一修复的第一版。
  • Kuba Piecuch 的贡献:v2 的两项关键改动(idle mask 初始化移入核心、新增静态键)均源自 Kuba 的建议,说明这是两人协作推进的修复。

一句话总结

sched_ext 的 idle mask 在调度器启用前不准确,导致 busy CPU 被误报为 idle;本系列通过提前启用追踪并同步刷新 CPU 状态来修复,同时将自测从竞态的远程检查改为稳定的本地不变量验证。