sched-ext discussion
[PATCHSET v2 sched_ext/for-7.3] sched_ext: Fix idle CPU state initialization and validation
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-ID | 20260731090334.2911948-1-arighi@nvidia.com |
| 来源 | sched-ext 邮件列表 |
明确目的
sched_ext 的内置 idle mask 在初始化时将所有 online CPU 标记为 idle,但 idle 状态追踪直到调度器完全启用后才启动。
这导致两个问题:
- 核心 bug:
ops.init()执行时以及此后直到 CPU 首次进入 idle 之前,busy CPU 会被错误地报告为 idle。 - 自测竞态:
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() 调用链:
static_branch_enable_cpuslocked(&scx_idle_tracking_enabled)— 启用追踪- 若
scx_builtin_idle_enabled为真,调用refresh_idle_masks()— 同步初始状态 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 突出问题
-
v3 将合并静态键:Kuba 指出
scx_idle_tracking_enabled与scx_builtin_idle_enabled可合并为一个。Andrea 已同意在 v3 中重做,用scx_builtin_idle_enabled做早期追踪,保留scx_enabled()检查来抑制ops.update_idle()回调。如果用户自行做 idle 追踪,内置 idle 被禁用,SCX 不需要追踪 idle 状态。 -
idle mask 反方向不变量不成立:Kuba 建议也检查"运行 idle task 时 idle bit 必须为 set"。Andrea 指出反方向不成立——BPF idle 选择 helper 可以在 CPU 仍在运行 idle task 时清除 idle bit(被选中),这是合法的。验证时不能假设"idle task → idle bit set"。
-
validate_local_idle_state()是否应扩展到ops.enqueue():Kuba 提出该不变量不仅适用于ops.select_cpu(),也应在ops.enqueue()中检查。Andrea 的回复中未明确反对,v3 可能扩展。 -
refresh_idle_masks()的 CPU 热插拔窗口:函数遍历for_each_online_cpu()并持 rq lock,但如果在遍历期间有 CPU 上线/下线,需要确认在线 CPU 集合的稳定性。代码注释声称"online CPU set is stable",但依赖调用上下文保证(scx_idle_enable()在cpus_read_lock下调用)。
版本演进
| 版本 | 关键改动 |
|---|---|
| v1 | idle 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_enabled 到 scx_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 状态来修复,同时将自测从竞态的远程检查改为稳定的本地不变量验证。