0/6 已展开

LLM 分析

selftests/bpf libarena:清理 arena spinlock qnodes 的重复副本

系列概况

  • 标题:[PATCH] selftests/bpf: Remove duplicate copies of the arena spinlock qnodes
  • 作者:Changwoo Min changwoo@igalia.com
  • 版本:v1(单封 patch,尚未发布 v2)
  • 规模:1 patch / 3 files changed, 15 insertions(+), 6 deletions(-)
  • 修改文件
    • tools/testing/selftests/bpf/libarena/include/bpf_arena_spin_lock.h
    • tools/testing/selftests/bpf/libarena/src/common.bpf.c
    • tools/testing/selftests/bpf/progs/arena_spin_lock.c
  • 代码统计:3 files / +15 / -6
  • Message-ID20260803001807.646357-1-changwoo@igalia.com
  • 完整性:完整。作者已同意基于 bpf-next HEAD rebase 发 v2,并已收到 Emil 的 Reviewed-by 标签。

补丁目的

本补丁是 selftests/bpf 下 libarena 的 cleanup

bpf_arena_spin_lock.h 头文件里直接定义了一个 64KB 的 qnodes[_Q_MAX_CPUS][_Q_MAX_NODES] 数组,导致任何 #include 该头文件的 BPF 翻译单元都会各自产出一份相同的数据副本。

虽然 __weak 在符号层面让所有副本解析为同一个 instance,但 BPF 的 bpftool gen object 合并策略和常规 ld 不一样:它只合并符号表,然后直接按顺序拼接每个输入的 .addr_space.1 字节。最终 .o / skel 文件里仍然带着 10 份未被任何符号引用的死数据。

实际数字对比(commit message 自带):

object before      after
-----------------------------------------------------
.addr_space.1 in libarena.bpf.o   676200     86376
libarena.skel.h                  2100123    892371
libarena_asan.skel.h             2641124   1466477

旧流程的问题

   bpf_arena_spin_lock.h
   +-------------------------------------------------+
   | struct arena_qnode __weak __arena __hidden     |
   | qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];              |  <- 64KB defined in header
   +-------------------------------------------------+
            |            |            |           |
            v            v            v v
       common.bpf.c  global.bpf.c  ...     TU-10 copies
            |            |            |           |
            +------ each TU emits its own 64KB copy ------+
                            |
                            v
                    bpftool gen object (concatenates .addr_space.1 bytes)
                            |
                            v
           libarena.bpf.o (~676KB of dead bytes)

__weak 让符号消解为一份,但拼接策略不识别 __weak,10 份里只有 1 份真正被引用,剩下 9 份是纯字节浪费。

新流程

   bpf_arena_spin_lock.h
   +-------------------------------------------------+
   | extern struct arena_qnode __arena __hidden     |
   | qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];              |  <- extern declaration
   +-------------------------------------------------+
            |                                       |
            v                                       v
   libarena/src/common.bpf.c             progs/arena_spin_lock.c
   +----------------------------+         +----------------------------+
   | struct arena_qnode ...     |         | struct arena_qnode ...     |
   | qnodes[...];  sole def     |         | qnodes[...];  test copy    |
   +----------------------------+         +----------------------------+
            |                                       |
            +-----------------+---------------------+
                              v
                  each contributes exactly one copy
                              |
                              v
              libarena.bpf.o (~86KB, ~87% smaller)

每个真正需要 qnodes 的程序 / libarena 自己显式提供一份定义,让 64KB 数据段只出现一次。

Patch 概览

/* libarena/include/bpf_arena_spin_lock.h (修改) */
-struct arena_qnode __weak __arena __hidden
- qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];
+extern struct arena_qnode __arena __hidden
+    qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];

/* libarena/src/common.bpf.c (新增) */
+struct arena_qnode __arena __hidden
+    qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];

/* progs/arena_spin_lock.c (新增,让测试自带 qnodes) */
+struct arena_qnode __arena __hidden
+    qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];

关键实现

  1. 头文件改成 extern 声明:删除 __weak,把数组变成普通外部符号声明,避免每个 TU 实例化一份 storage。
  2. libarena 公共 TU 提供唯一存储src/common.bpf.c 是 libarena 编译时一定会包含的 TU,把 qnodes 的实体放这里,所有 libarena 内部 TU 共享同一份。
  3. 测试程序自给自足arena_spin_lock.c 测试目标单独再放一份定义,让它脱离 libarena 时也能独立编译/链接(BPF selftest 目标不一定和 libarena 一起链接)。
  4. 修饰符保持一致:依旧带 __arena __hidden,确保进入 BPF arena 地址空间并对 verifier 隐藏符号。

类比

把这事想成小区公告栏:旧实现里 10 个单元每家门口贴一份"今日停电通知",大家看的是同一张通知,但墙面上总共有 10 张一模一样的纸。清洁阿姨(gen object)只管把所有纸张叠在一起塞进文件袋,不管是不是同一张。

新方案只在物业前台(common.bpf.c)放一份底稿,每个单元需要时复印一张带走,省下 9 张纸;测试小屋(arena_spin_lock.c)因为不在物业系统里,自己也单独留一张底稿。

Highlight:风险与注意点

  1. __weak 不等于数据段去重:BPF gen object 的合并只到符号层,.addr_space.1 是字节级拼接,"看起来只有一份"和"实际只有一份"是两件事。后续如果有人把别的 __weak 数组也写到 libarena 头里,需要警惕同样的膨胀。
  2. 测试程序必须自带定义arena_spin_lock.c 单独复制了一份 qnodes;如果将来又新增 #include bpf_arena_spin_lock.h没有附上定义的目标,链接阶段会缺符号,需要在 commit log / 注释里继续强调这个约定。
  3. asan 与非 asan 都要测:commit message 只说了 -t arena_spin_lock -t libarena,但 asan skel 才是体积收益最大的一份(约 -1.17 MB),后续 review 可以追加 ASan 构建的字节对比作为佐证。
  4. v2 需要 rebase:Daniel 反馈无法干净应用到 bpf-next HEAD,需注意同期是否有针对 __arena / __hidden 的语义变动,否则 v2 在重写过程中可能引入风格偏移。
  5. 作者与审阅进度:Kumar 的两封回复都只是引用原文,没有提技术意见;正式 review 意见目前只有 Emil 给的 Reviewed-by 和 Daniel 的 rebase 要求,v2 一出即可合入。

版本变化

  • v1 -> v2(未发布):作者承诺基于 bpf-next HEAD rebase 后重发;Emil 在 reply #5 给出 Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>,v2 应保留该 tag。

一句话总结

把 libarena 头里 64KB 的 qnodes 定义改成 extern,由 common.bpf.carena_spin_lock.c 各自提供一份,把 .addr_space.1 段砍掉约 87%、asan 版 skel 直接砍掉 1.1 MB,等 v2 rebase 后即可合入。