0/4 已展开

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-ID20260726064754.378671-1-arighi@nvidia.com
目标分支sched_ext/for-7.2-fixes
来源sched-ext 邮件列表

明确目的

sched_extallowed_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 的目标

  1. 将验证从"远程 CPU 的 idle 状态"改为"本地 CPU 的 idle 状态"——本地 CPU 不可能在本回调运行期间经历 idle re-pick,因此是稳定不变量。
  2. 增加对 selected CPU 的 allowed domain 和 task affinity 的验证。
  3. 在测试启动时引导 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 突出问题

  1. refresh_idle_masks 的原子性:userspace 用 __atomic_store_n(..., __ATOMIC_RELEASE) 写,BPF 侧用 volatile bool 读。两者之间没有显式的 BPF 内存屏障,依赖 BPF 默认的内存序语义。在极端架构上是否足够安全,值得确认。

  2. scx_bpf_test_and_clear_cpu_idle() 的副作用validate_local_idle_state() 在发现 idle 状态不一致时会调用 scx_bpf_test_and_clear_cpu_idle(),这本身会清除 idle 标记。这既是验证也是修复,但意味着验证逻辑改变了被验证对象的状态。

  3. 引导阶段与严格验证的边界:如果 refresh_idle_masks() 在某些 CPU 上执行失败(sched_setaffinity 返回错误),部分 CPU 的 idle 状态可能未被正确初始化,后续严格验证可能在那些 CPU 上误报。

  4. 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 初始化正确。