0/15 已展开

LLM 分析

selftests BPF 工具链:共享 lib.bpf.mk 合并与跨 CPU memcg rstat flush 验证

系列概况

  • 标题:[PATCH v2 0/4] selftests: shared lib.bpf.mk for building BPF progs and skeletons
  • 作者:Ziyang Men (Meta)
  • 版本:v2
  • 规模:4 patches,新增约 1275 行,删除约 280 行;触及 11 个文件
  • 修改文件
    • 新建:tools/testing/selftests/lib.bpf.mk(247 行)、memcg_stat_cross_cpu.bpf.c(86 行)、memcg_stat_cross_cpu.h(27 行)、test_memcg_stat_cross_cpu.c(780 行)
    • 修改:selftests/cgroup/{Makefile,.gitignore,config}cgroup_util.{c,h}selftests/hid/Makefile(182→92 行)、selftests/sched_ext/Makefile(153→35 行左右)
  • 代码统计:约 +1275 / −280
  • Message-ID锚点<20260721174833.1232771-1-ziyang.meme@gmail.com>(cover);其余为 -2..-5 子补丁
  • 完整性:cover letter + 4 个 patch + 若干 bot报告 + 至少 3 封 maintainer/作者交互回复;v2 相比 v1 的 changelog 列在 cover letter末尾(泛化 lib.bpf.mk、可配置输出目录、新增 hid/sched_ext 转换 patch)

补丁目的

selftests 下 bpf/sched_ext/hid/ 三个子系统各自复制粘贴了 ~100–140 行近乎相同的 Makefile 片段:自己编译 in-tree libbpf.a、构建 host bpftool、从 vmlinux BTF 生成 vmlinux.h、把 *.bpf.c 编成 BPF 目标并生成 skeleton。随着 BPF selftest 数量增长,这种重复不可持续。本系列把这些公共工具链抽到一个可被包含的片段 tools/testing/selftests/lib.bpf.mk,并以三个使用者上链验证它:

  1. 新增的 cgroup memcg 正确性测试(selftests/cgroup
  2. 已存在的 selftests/hid(legacy progs/<name>.c 布局)
  3. 已存在的 selftests/sched_ext(modern *.bpf.c + .bpf.skel.h + subskeleton)

副产物:patch 2/4 还顺手补齐 memcg BPF kfunc 跨 CPU 的正确性测试(之前 cgroup_iter_memcg 只断言大于零,且不触发 rstat flush)。

旧流程的问题

-三个 Makefile 各自抄一遍同样的工具链定义(HOSTCC/HOSTLD/CLANG、get_sys_includesVMLINUX_BTF_PATHS、bpftool/sketch 生成规则)。

  • hid 自己的 BPF 编译 没有开 -Wall,所以源里遗留几个未用变量也没人提醒;其它两个开了 -Werror
  • sched_ext 还要手写 skel subskel 三连 gen + diff 流程(linked1/2/3)。
  • 新写一个 BPF selftest 必须先把这 ~150 行抄一遍,开销高、易飘移。

新流程

调用方只需:

BPF_SRCS := foo.bpf.c [bar.bpf.c ...]
OVERRIDE_TARGETS := 1
include ../lib.mk
include ../lib.bpf.mk

$(OUTPUT)/foo_test: foo_test.c $(BPF_SKELS)
$(call bpf_link,$@,$<)

lib.bpf.mk 自动负责:

  • 探测 vmlinux BTF路径(O=/in-tree//sys/kernel/btf/vmlinux//boot/vmlinux-…)并 bpftool btf dumpvmlinux.h
  • clang --target=bpf -mcpu=v3*.bpf.c*.bpf.o
  • bpftool gen skeleton(可选用 subskeleton)生成 *.skel.h / *.subskel.h
  • 提供 include 路径、bpf_link 链接宏、BPF_LDLIBS(含可选 -lzstd
  • 可调旋钮:BPF_PROG_EXT(默认 .bpf.c)、BPF_EXTRA_HDRS/BPF_EXTRA_CFLAGSBPF_SKEL_EXTBPF_GEN_SUBSKELBPF_OBJ_DIR/BPF_SKEL_DIR
  • 交叉编译时把 host/target libbpf 拆到两个 build 目录,避免 host bpftool 用错编译器

Patch 概览

Patch作用关键点
1/4新增 lib.bpf.mk 共享片段247 行;暴露上述全部 knob
2/4新增 cgroup memcg_stat_cross_cpu 测试首个 cgroup/ 下 BPF selftest;首个 lib.bpf.mk 的 cgroup 消费者
3/4selftests/hid/Makefile 改用片段182→92 行;保留 legacy progs/*.c 布局(BPF_PROG_EXT=.c
4/4selftests/sched_ext/Makefile 改用片段153→35 行;用 .bpf.skel.h + subskeleton;BPF_CFLAGS 全量覆盖(不加 -Werror)

关键实现

  • vmlinux BTF 自发现:依次尝试 $(O)/vmlinux$(KBUILD_OUTPUT)/vmlinux$(top_srcdir)/vmlinux/sys/kernel/btf/vmlinux/boot/vmlinux-$(uname -r),全部 miss 直接 $(error ...)
  • 跨编译器一致性:先 include Makefile.archMakefile.include,把 lib.mk 设好的 CC(带 --target=arch)保存为 lib_bpf_mk_saved_cc 再恢复,避免 LLVM=1 cross 编译时 Makefile.include 把 CC 重置成裸 clang 而丢 --target
  • host vs target libbpfBPFOBJ(target,给 PIE 测试二进制链)只在非交叉编译时与 HOST_BPFOBJ(host,给 bpftool)相等;不等时另起一条规则并以 HOSTCC/HOSTLD 编译。
  • clang system includes:通过 clang -v -E 输出解析 -idirafter,避免 shadow真实 include;RISC-V 额外按 __riscv_xlen 注入 -D__riscv_xlen=-D__BITS_PER_LONG=
  • BPF 编译规则-O2 --target=bpf -mcpu=$(CLANG_BPF_CPU)CLANG_BPF_CPU 通过 clang --target=bpf -mcpu=help 探测 v3,否则回退 v2。BPF_CFLAGS 默认带 -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) -fms-extensions -Wno-microsoft-anon-tag -Wno-compare-distinct-pointer-types
  • skeleton 生成:通过 bpftool gen object <linked1.o> + gen skeleton name <basename>;subskeleton 由 BPF_GEN_SUBSKEL := 1 触发(仅 sched_ext 使用)。
  • patch 2 测试要点
    • BPF 端用 SEC("iter.s/cgroup")DESCENDANTS_PRE 遍历整个 cgroup 子树;在 seq_num == 0(根)只 flush 一次,因为 css_rstat_flush已经是子树范围;每 cgroup 用 bpf_get_mem_cgroup 取 memcg 后调 bpf_mem_cgroup_page_state/bpf_mem_cgroup_vm_events 取统计并写入以 kn->id 为 key 的 hash map。
    • 用户态用 name_to_handle_at(2)(cgroupfs handle恒为 8 字节)拿到与 BPF 端 cgrp->kn->id 对齐的 id,把 hash 结果与 memory.stat 文本比较;两次独立 round(一次 BPF、一次文件)从同一棵新子树同一起点出发。
    • 验证性质:(a) flush 后每个 leaf 的 anon ≥ 该 leaf 被 charge 的量;(b) flush 后 leaf累加 == root 量。
    • 新增 helper cg_get_id()(基于 name_to_handle_at)放进 cgroup_util
  • patch 3/4:用 BPF_EXTRA_HDRS 把各自共享 BPF header 当 extra prerequisite;hid 因源里有几个未用变量,传 -Wno-unused-variable;sched_ext 因不是 -Werror clean 在 include 后整体覆盖 BPF_CFLAGS(片段对 BPF_CFLAGS 的展开是 lazy 的,安全)。
                       +-----------------------+
                       | *.bpf.c (per-sub)   |
                       +----------+------------+
                                  |
                                  v  clang --target=bpf -mcpu=v3
                       +----------+------------+
                       |     *.bpf.o           |
                       +----------+------------+
                                  |
                                  v  bpftool gen skeleton [+subskel]
                       +----------+------------+
                       | *.skel.h / *.subskel.h|
                       +----------+------------+
                                  |
                                  v  bpf_link macro (BPF_LDLIBS)
 +--------------+ +------+------+ +-------------+
   | libbpf.a     | <---- |  test binary| ----> | cgroupfs |
   | (in-tree)    |       | (selftest)  |        | memory.stat  |
   +--------------+       +-------------+        +-------------+
                                  ^
                                  |
   +--------------+               | bpftool gen skeleton -> BPF kfuncs (memcg)
   | vmlinux.h    | <--- vmlinux BTF dump --- bpftool   +--------------+

类比

lib.bpf.mk 想象成小区里的 公共厨房

  • 旧流程像每户租户自己买锅、买炉子、自己通燃气,重复又危险(每家 Makefile 各抄一遍)。
  • 新流程里物业统一建好中央厨房:锅、炉、抽油烟机(libbpfbpftoolvmlinux.h)都已就位,每户(bpf/hid/sched_ext/cgroup/)只要点菜(BPF_SRCS)就行。业主还可以微调口味(BPF_EXTRA_CFLAGSBPF_GEN_SUBSKEL)。
  • patch 2 那条 memcg 测试像 物业安排的试菜:故意把同一道菜的订单分散到不同外卖员(多个 CPU 的 per-cpu rstat),最后验收员(BPF kfunc + 文件读)核对同一份账单是否一致——只有当所有外卖员把数据汇总到前台(rstat flush)才不会出现漏单。

Highlight:风险与注意点

  1. OVERRIDE_TARGETS := 1 必须先于 lib.mk 包含,否则 lib.mk 自带的链接规则会和 bpf_link 重复或冲突;cgroup Makefile 已显式重新补一条链接规则覆盖纯 C 用例。
  2. CC 保存/恢复:在 LLVM=1 CROSS_COMPILE=arm64- 这类场景下,Makefile.include 会把 CC 重置成裸 clang,必须用 lib_bpf_mk_saved_cc 还原,否则 host bpftool 与 target libbpf 工具链错位(-Wstrict-aliasing=3 等 gcc-only flag 会让 clang 罢工)。
  3. vmlinux BTF 找不到会 hard error:CI 环境若未生成 vmlinux 或未挂载 /sys/kernel/btf/vmlinux,整目录 build 直接挂;自测试机(VM)需要保留 BTF on。
  4. BPF_GEN_SUBSKELBPF_SKEL_EXT 是新引入的旋钮,未来若还想让 BPF_LDLIBS 跟随 RELEASE/SAN_CFLAGS 走,需要 v3 按 Viktor 的建议补 OPT_FLAGS/EXTRA_CFLAGS 透传——目前硬编码 -g -O0/-g -O2
  5. patch 2 选址争议:JP Kobryn 建议沿用现有 cgroup_iter_memcg(在 selftests/bpf 下)扩展,作者答复是 v1 评审与 bpf reviewer 商量后觉得“测的是 cgroup 功能而非 BPF 本身”,并且本系列也同时提供共享 lib.bpf.mk,故放在 selftests/cgroup/ 作为首个新消费者更合适。结论倾向:保留新文件,但建议在 changelog 里贴一段与 cgroup_iter_memcg 的覆盖差异表,便于未来去重。
  6. bot 报告:sashiko-bot 与 bpf-ci bot 分别发了 v2 抓取片段和 GitHub Actions run(bpf/actions/runs/29908293077),需关注 CI 是否绿;目前邮件里只有 URL 与 run id,没有失败日志。
  7. sched_ext 整体覆盖 BPF_CFLAGS:作者故意去掉了 -Werror,因为 scx 现存 BPF 代码并非全 clean;后续若上游修干净,可重新打开 -Werror 而不再覆盖。

版本变化

v1 → v2(作者 changelog):

  • 泛化 lib.bpf.mk:加入 BPF_PROG_EXTBPF_EXTRA_HDRS/BPF_EXTRA_CFLAGSBPF_SKEL_EXTBPF_GEN_SUBSKELBPF_OBJ_DIR/BPF_SKEL_DIR,以便 hid、sched_ext 也用得起来。
  • 新增 patch 3(hid)与 patch 4(sched_ext),把这两处复制粘贴的工具链改成 include ../lib.bpf.mk

v2 review 后作者已承诺 v3 的改动(截至 2026-08-12):

  • 把 target/host libbpf 和 host bpftoolEXTRA_CFLAGS/OPT_FLAGS 抽出来跟随调用方,允许 make RELEASE=1-O2、附加 -DFOO
  • 评估是否进一步用 USERCFLAGS(lib.mk 定义)作为传递通道。

一句话总结

把 selftests 里散落在 bpf/hid/sched_ext/ 三处的 ~150 行 BPF 工具链抽成可复用片段 lib.bpf.mk,同时新增 cgroup/memcg_stat_cross_cpu 测试覆盖 memcg BPF kfunc 与 mem_cgroup_flush_stats() 在跨 CPU 场景的正确性,并清理 v2/v3 路线上的 EXTRA_CFLAGS 透传问题。