sched-ext discussion
[PATCH sched_ext/for-7.2-fixes] selftests/sched_ext: Make allowed_cpus idle validation race-free
LLM 分析
修复 allowed_cpus 自测空闲验证竞态条件
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | [PATCH sched_ext/for-7.2-fixes] selftests/sched_ext: Make allowed_cpus idle validation race-free |
| 作者 | Andrea Righi (arighi@nvidia.com) |
| 版本 | 单 patch(无版本号) |
| 规模 | 2 文件,+82 / -7 |
| Message-ID | 20260726064754.378671-1-arighi@nvidia.com |
| 目标分支 | sched_ext/for-7.2-fixes |
| 来源 | sched-ext 邮件列表 |
明确目的
sched_ext 的 allowed_cpus 自测用来验证:当 BPF 调度器通过 scx_bpf_select_cpu_and() 选中一个 CPU 后,该 CPU 不应再被标记为 idle。
旧代码的问题:旧验证逻辑在 ops.select_cpu() 和 ops.enqueue() 中检查被选中的远程 CPU 是否仍不在 idle mask 中。但远程 CPU 在被选中后、验证之前,可能经历一次 idle-to-idle re-pick,导致它被重新标记为 idle。这使得验证本质上存在竞态,可能产生误报(scx_bpf_error 误触发)。
本 patch 的目标:
- 将验证从"远程 CPU 的 idle 状态"改为"本地 CPU 的 idle 状态"——本地 CPU 不可能在本回调运行期间经历 idle re-pick,因此是稳定不变量。
- 增加对 selected CPU 的 allowed domain 和 task affinity 的验证。
- 在测试启动时引导 idle mask 的初始化,确保严格验证开始前 idle mask 已正确。
遍历代码
1. BPF 侧:allowed_cpus.bpf.c
新增全局变量 refresh_idle_masks:
volatile bool refresh_idle_masks;
这是一个 BPF 与 userspace 共享的标志,用于引导阶段允许 idle mask 修复。
替换 validate_idle_cpu() 为两个新函数:
validate_local_idle_state():检查当前(本地)CPU 的 idle 状态。通过 RCU 读取当前 CPU 上运行的任务,判断其PF_IDLE标志。如果本地 CPU 正在运行非 idle 上下文却被标记为 idle,则报错——除非refresh_idle_masks为真(引导阶段豁免)。
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) && !refresh_idle_masks)
scx_bpf_error("running CPU %d should be marked as busy", cpu);
关键注释:本地 CPU 不可能在本回调运行期间经历 idle re-pick,因此该检查是无竞态的。
validate_selected_cpu():验证被选中的 CPU 是否在 allowed domain 和 task affinity mask 中。这比旧代码更全面——旧代码只检查 allowed domain 与 affinity 的子集关系。
修改 ops.select_cpu() 和 ops.enqueue():
- 调用
validate_local_idle_state()代替validate_idle_cpu()。 - 调用
validate_selected_cpu()做域名/亲和性验证。
新增 ops.running() 回调:
void BPF_STRUCT_OPS(allowed_cpus_running, struct task_struct *p)
{
if (refresh_idle_masks)
scx_bpf_test_and_clear_cpu_idle(bpf_get_smp_processor_id());
}
引导阶段,当任务在某个 CPU 上运行时,主动清除该 CPU 的 idle 标记,修复初始状态。
2. Userspace 侧:allowed_cpus.c
新增 refresh_idle_masks() 函数:
通过 sched_setaffinity() 将当前任务逐个绑定到每个可用 CPU,然后 sched_yield() 让 ops.running() 在该 CPU 上执行,从而清除 idle 标记。完成后恢复原始亲和性。
修改 run() 测试流程:
skel->bss->refresh_idle_masks = true;
SCX_FAIL_IF(refresh_idle_masks(), "Failed to refresh idle CPU state");
__atomic_store_n(&skel->bss->refresh_idle_masks, false, __ATOMIC_RELEASE);
先设置 refresh_idle_masks = true,运行引导,然后原子地释放标志。此后严格验证生效。
ASCII 流程图
旧逻辑(有竞态):
select_cpu() -> 选中 remote CPU X
| +-- 同时: idle re-pick 可把 X 重新标为 idle
v
validate_idle_cpu(X) -> "X 应该 busy,但 idle mask 里又有 X" -> 误报!
新逻辑(无竞态):
select_cpu() -> 在 local CPU 上运行
|
v
validate_local_idle_state():
读取 local CPU 的 curr task
curr 是 idle? ──Yes──> OK
|
No
v
local CPU 在 idle mask 中? ──Yes──> scx_bpf_error (真正错误)
|
No
v
OK (local CPU 不可能被 re-pick, 无竞态)
引导阶段:
userspace: refresh_idle_masks = true
|
v 逐 CPU 绑定 + yield
ops.running() -> scx_bpf_test_and_clear_cpu_idle() -> 清除 idle 标记
|
v
userspace: refresh_idle_masks = false
|
v
严格验证开始 (idle mask 已初始化)
概念类比
想象一个酒店前台系统:旧方法是前台把客人分配到某间房后,打电话去确认"这间房应该标记为已入住"。但电话打过去时,可能正好有另一个前台把同一间房重新标记为空房(idle re-pick),导致确认失败——这是竞态。
新方法是:前台不再打电话确认远程房间,而是确认自己所在的前台——如果前台此刻正在为客人办理入住(非 idle),那这个前台就不应该被标记为空闲。因为你在做这件事的同时不可能有人把你标记为空闲,所以不存在竞态。
引导阶段则像是:酒店刚开业时,系统里的房间状态可能不准确,所以先让每个房间都"走一遍入住流程"(ops.running()),把状态刷新正确,之后才开始严格检查。
Highlight 突出问题
-
refresh_idle_masks的原子性:userspace 用__atomic_store_n(..., __ATOMIC_RELEASE)写,BPF 侧用volatile bool读。两者之间没有显式的 BPF 内存屏障,依赖 BPF 默认的内存序语义。在极端架构上是否足够安全,值得确认。 -
scx_bpf_test_and_clear_cpu_idle()的副作用:validate_local_idle_state()在发现 idle 状态不一致时会调用scx_bpf_test_and_clear_cpu_idle(),这本身会清除 idle 标记。这既是验证也是修复,但意味着验证逻辑改变了被验证对象的状态。 -
引导阶段与严格验证的边界:如果
refresh_idle_masks()在某些 CPU 上执行失败(sched_setaffinity返回错误),部分 CPU 的 idle 状态可能未被正确初始化,后续严格验证可能在那些 CPU 上误报。 -
Kuba Piecuch 的回复:Kuba 引用了 patch 但截断的邮件内容中未显示其具体评论,可能还有后续讨论未在此线程中完整呈现。
版本演进
目前仅有一个版本,无版本迭代。
与其他相关 patch 系列的关联
- 此 patch 目标为
sched_ext/for-7.2-fixes,是 sched_ext 7.2 合并窗口的修复。 allowed_cpus测试本身是 sched_ext 自测基础设施的一部分,与scx_bpf_select_cpu_and()API 的正确性验证直接相关。
一句话总结
将 allowed_cpus 自测的 idle 验证从"检查远程 CPU 的 idle 状态(本质竞态)"改为"检查本地 CPU 的 idle 状态(稳定不变量)",并增加引导阶段确保 idle mask 初始化正确。