0/1 已展开

LLM 分析

sched_ext:扩展 create_dsq selftest 覆盖 DSQ 重复创建与 ID 复用

系列概况

  • 标题[PATCH] selftests/sched_ext: Cover duplicate DSQ creation and ID reuse
  • 作者:Tianyi Chen <hi@tychen.cc>
  • 版本:单封 PATCH(非系列)
  • 规模:2 文件改动,+38/-1
  • 修改文件
    • tools/testing/selftests/sched_ext/create_dsq.bpf.c(+35)
    • tools/testing/selftests/sched_ext/create_dsq.c(+3/-1)
  • Message-ID20260906144044.849222-1-hi@tychen.cc
  • 完整性:完整(含 diff、commit message、Link、Assisted-by、Signed-off-by)

补丁目的

把 sched_ext DSQ ID 生命周期 API 契约钉死在 selftest 上:

  1. 重复 scx_bpf_create_dsq 必须返回 -EEXIST
  2. 失败的重复创建不能破坏原 DSQ 的可访问性;
  3. destroy_dsqdsq_nr_queued 必须返回 -ENOENT
  4. 同 ID 销毁后允许重新创建;
  5. 1024 次迭代全部跑通作为空队列回归基线。

旧流程的问题

create_dsq_init 只在循环里 create + destroy,没断言任何错误码:

  • 重复创建是否真返回 -EEXIST?没测;
  • 失败的重复创建是否破坏原 DSQ?没测;
  • destroy 后 dsq_nr_queued 返回什么?没测;
  • 同 ID 销毁后能否复用?没测;
  • 1024 次循环只是计数,无法发现中段回归。

新流程

每次迭代扩成九步:destroy → create → create(断言 -EEXIST)→ nr_queued==0 → destroy → nr_queued==-ENOENT → recreate → nr_queued==0 → 计数++。

用户态 run() 追加 SCX_EQ(nr_lifecycle_tests, 1024) 把 BPF 侧结果与迭代次数对齐。

关键实现

BPF 侧新增 u32 nr_lifecycle_tests,每次迭代核心逻辑:

bpf_for(i, 0, 1024) {
    scx_bpf_destroy_dsq(i);            /* 清场 */
    err = scx_bpf_create_dsq(i, -1);   /* 1) 首次创建 */
    err = scx_bpf_create_dsq(i, -1);   /* 2) 重复 -> -EEXIST */
    err = scx_bpf_dsq_nr_queued(i);    /* 3) 原 DSQ 仍可读 */
    scx_bpf_destroy_dsq(i);
    err = scx_bpf_dsq_nr_queued(i);    /* 4) 销毁后 -> -ENOENT */
    err = scx_bpf_create_dsq(i, -1);   /* 5) 复用 ID */
    err = scx_bpf_dsq_nr_queued(i);    /* 6) 新 DSQ 仍空 */
    nr_lifecycle_tests++;
}

用户态只改两行:计数器断言 + description 文案更新。

ASCII 流程

                 +-----------+
   i  in [0,1024) |---1. destroy(i)----+
                 +-----------+          |
                          |             v
                          |   +------------------+
                          |   | 2. create(i) OK  |
                          |   +------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          |   | 3. create(i) -> EEXIST|
                          |   +----------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          |   | 4. nr_queued(i) == 0 |
                          |   +----------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          |   | 5. destroy(i)        |
                          |   +----------------------+
                          |             |
                          |             v
                          |   +--------------------------+
                          |   | 6. nr_queued(i) == -ENOENT|
                          |   +--------------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          |   | 7. create(i) OK reuse|
                          |   +----------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          |   | 8. nr_queued(i) == 0 |
                          |   +----------------------+
                          |             |
                          |             v
                          |   +----------------------+
                          +---|9. nr_lifecycle_tests++|
                              +----------------------+

  After loop: user-space run() -> SCX_EQ(nr_lifecycle_tests, 1024)
                              -> PASS / FAIL

DSQ 状态机

       create(i)            create(i) --EEXIST--> (no-op)
   +-----------+  +-----------+ +-----------------------+
   |   Free    |->|  Active   |-|  Active (unchanged)  |
   +-----------+  +-----------+ +-----------------------+
        ^             |  ^              |
        |   destroy   |  | recreate(i)  | destroy
        |             v  |              v
        |       +-----------+      +-----------+
        |       | Destroyed |      | Destroyed |
        |       +-----------+      +-----------+
        |             |   nr_queued==-ENOENT
        +-------------+----------------+
                   next iteration

类比

把 DSQ ID 想成酒店前台的房号:

  • 首次 create(i):发房卡 → Active;
  • create(i):前台说「已有人」→ -EEXIST,但原房卡仍有效;
  • destroy(i):客人退房 → 房间不存在;
  • nr_queued(i):客服查房 → -ENOENT
  • create(i):同房号重新挂出 → ID 复用成立;
  • 累计 1024 次:连续 1024 次入住都没出房务事故。

Highlight:风险与注意点

  1. 只覆盖空 DSQ:commit message 明说「without concurrent enqueueing or destruction of a nonempty queue」,无法捕获有任务被消费时的并发销毁竞态,需另写并发 selftest。
  2. -EEXIST 是 API 契约:若 sched_ext 改为 refcount/race-tolerant 创建语义,本测试会误报。
  3. 依赖前置 patchLink: 指向 Z-OZ7tJWhRZbUk1l@gpd3/,本补丁基于其 diff 二次增强,需先 apply 前置 patch 才能匹配基础结构,但提交时未在 commit message 注明依赖。
  4. debug build 风险BPF_STRUCT_OPS_SLEEPABLE 在 lockdep/KEI 等 debug 构建下可能被放慢,1024 次 BPF 调用耗时不可控。
  5. 硬件覆盖不足:作者仅在双核 matching-kernel VM 验证,16 CPU/NUMA 拓扑上是否一致未经验证。
  6. 全局命名冲突nr_lifecycle_tests 是 BSS 全局,未来同类 selftest 共用 BPF skeleton 时需注意命名。

一句话总结

sched_ext 把 create_dsq selftest 的每次迭代从「create+destroy」扩成九步 DSQ 生命周期,并用 1024 次计数器断言把 -EEXIST / -ENOENT / ID 复用这套 API 契约钉死在空队列场景上。