使用 libbpf + skeleton 编写 Futex 工具时,问题通常出现在四个阶段:对象没有正确生成、程序没有通过 verifier、程序没有 attach 到预期 hook,或者程序已经运行但 Map 中没有数据。几个阶段的表现很像,必须从前向后逐层排查。

一、从编译结果确认对象是否完整

clang -O2 -g -target bpf -c futexlat.bpf.c -o futexlat.bpf.o
bpftool gen skeleton futexlat.bpf.o > futexlat.skel.h
clang futexlat.c -lbpf -lelf -lz -o futexlat

CO-RE 依赖有效 BTF:

bpftool feature probe
bpftool btf dump file /sys/kernel/btf/vmlinux | head

出现 failed to find valid kernel BTFstruct_ops requires kernel CONFIG_DEBUG_INFO_BTF=y 时,应先检查内核配置和工具版本。

确认 Map 已进入 skeleton

grep -A3 hists futexlat.skel.h

预期存在:

struct {
    struct bpf_map *hists;
} maps;

如果 maps.hists 不存在,应检查 .bpf.c 中的 Map 定义、section 和实际编译输入,而不是继续修改查询逻辑。

如果 BPF 侧已经调用 bpf_map_lookup_or_try_init(&hists, ...),并继续向 histp->comm 或直方图槽位写入,就能够确认该 Map 确实被程序引用,不应把问题简单归因于编译器删除未使用 Map。

二、分开检查 open、load 和 attach

obj = futexlat_bpf__open();
if (!obj) {
    fprintf(stderr, "failed to open BPF object\n");
    return 1;
}

err = futexlat_bpf__load(obj);
if (err) {
    fprintf(stderr, "failed to load BPF object: %d\n", err);
    goto cleanup;
}

err = futexlat_bpf__attach(obj);
if (err) {
    fprintf(stderr, "failed to attach BPF programs: %d\n", err);
    goto cleanup;
}

开启 libbpf_set_print(libbpf_print_fn),同时查看:

dmesg | tail -n 50
bpftool prog list | grep futex
sudo cat /sys/kernel/debug/tracing/trace_pipe

三、检查 Map fd、迭代逻辑和共享结构

int fd = bpf_map__fd(obj->maps.hists);
if (fd < 0) {
    fprintf(stderr, "invalid hists map fd: %d\n", fd);
    return -1;
}

EBADF 表示无效文件描述符,与“Map 为空”不是一回事。Map 为空时,bpf_map_get_next_key() 返回失败,用户态应结束迭代,不能继续拿未初始化的 next_key 做 lookup。

可以先写一个只打印 fd、调用一次 bpf_map_get_next_key() 的最小程序,暂时移除格式化输出和直方图遍历。若 fd 为正数但 lookup 仍失败,再检查 Map 类型、key/value 大小和传入指针,而不是继续排查 load 阶段。

__u32 key, next_key;
int err = bpf_map_get_next_key(fd, NULL, &next_key);

while (!err) {
    struct hist hist = {};
    if (bpf_map_lookup_elem(fd, &next_key, &hist)) {
        fprintf(stderr, "lookup failed: %s\n", strerror(errno));
        break;
    }
    key = next_key;
    err = bpf_map_get_next_key(fd, &key, &next_key);
}
错误日志应同时保留函数返回值和 `errno`。只打印一个 `-9`,无法判断它来自 libbpf、系统调用包装还是程序自己的错误转换。

检查共享结构体

用户态和 BPF 侧必须使用一致的 key/value 布局,尤其要检查字段类型、顺序、数组长度和对齐。

#define MAX_SLOTS 64
struct hist {
    char comm[16];
    __u64 slots[MAX_SLOTS];
};

最好将共享结构放进同一个头文件。结构体不一致通常不会使 fd 无效,却会造成读取失败或数据错位。

四、程序已 attach,为什么仍然没有数据

Map 存在且 fd 正常,却没有任何 key 时,可以增加临时计数器:

volatile __u32 switch_counter;

static __always_inline int handle_switch(/* ... */)
{
    __sync_fetch_and_add(&switch_counter, 1);
    return 0;
}

用户态读取 obj->bss->switch_counter,或在 BPF 程序中临时加入 bpf_printk() 并观察 trace_pipe

过滤条件和测试负载

如果目标 PID 没有进入 Futex 慢路径,Map 为空是正常结果。调试阶段可以暂时设置:

obj->rodata->targ_tgid = 0;

再制造可控负载:

sudo stress-ng --futex 2 --timeout 5s
grep futex_wait /proc/kallsyms

全局模式有数据、指定 PID 后无数据时,应优先检查 PID/TGID 语义和过滤条件;测试负载也无法触发时,再检查 attach 点和内核符号。

对于原始 futexlat,还要确认 handle_switch() 是否确实位于已 attach 的 futex_waitfutex_wake 或相应返回探针路径中。仅仅生成了 skeleton,并不能证明这些回调在当前内核和测试负载下会执行。

五、将 struct_ops 问题分开并形成排查顺序

LAVD 等 sched_ext 程序可能出现 failed to load: -22BPF_STRUCT_OPS 依赖内核 BTF、对应内核功能和结构定义,不应与 Futex kprobe/tracepoint 问题混在一起。

当前实验笔记还记录过 errno 22 (EINVAL)errno 14 (EFAULT)。这两个 errno 只能说明 loader 最终收到的错误,不能单独作为根因;发表前需要保留对应的完整 verifier 日志、内核版本和触发命令。

[Warn] libbpf: prog 'lavd_select_cpu': failed to load: -22
[Warn] libbpf: failed to load BPF skeleton 'bpf_bpf': -22
Error: Failed to load BPF program: Invalid argument (os error 22)
cat /sys/kernel/sched_ext/state /sys/kernel/sched_ext/*/ops 2>/dev/null
bpftool struct_ops list
bpftool btf dump file /sys/kernel/btf/vmlinux | head

推荐排查顺序

顺序 检查项 目标
1 BTF、编译和 skeleton 对象及 Map 已生成
2 open skeleton 可以打开
3 load 与 verifier 程序进入内核
4 attach hook 已建立
5 Map fd 用户态句柄有效
6 计数器和 trace_pipe 程序实际执行
7 PID/TGID 和测试负载 确实存在事件
8 Map 迭代和结构体 数据读取正确

按阶段排查后,“程序没有加载”“probe 没触发”和“Map 本来为空”就不会再混为同一个问题。

参考资料


文章作者: 易百分
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 易百分 !
 上一篇
Linux CPU 调度与 sched_ext 基础:从任务唤醒到 CPU 执行 Linux CPU 调度与 sched_ext 基础:从任务唤醒到 CPU 执行
按照任务唤醒、入队、选取、上下文切换和停止运行的顺序,介绍 Linux CPU 调度主流程,以及 sched_ext、struct sched_ext_ops、DSQ 与用户态加载程序之间的关系。
2026-04-09
下一篇 
Futex 性能分析 Futex 性能分析
使用 eBPF 观测 Futex 的等待与唤醒路径,分析锁竞争、等待延迟和观测工具自身开销,是《Futex 概述》的后续。
2026-03-09
  目录