两天有空,继续更新一篇有关 eBPF BCC 框架尾调用的内容。
eBPF 技术很新,能够参考的中文资料很少,而对于 BCC 框架而言,优秀的中文介绍和教程更是凤毛麟角。我尝试去网上检索有关尾调用的中文资料,BCC 框架的几乎没有。即使找到了,这些资料也难以给出可供参考和正确运行的例子。
BCC 框架的中文资料也就图一乐,真正有指导意义的,还得去看 Brendan Gregg 大神的博客和 bcc 项目。
既然如此,我来抛砖引玉,就简单介绍一下 eBPF 尾调用在 BCC 框架中是如何应用的吧。
1 何为尾调用?
引用 ebpf.io 网站的一句介绍:“尾调用允许 eBPF 调用和执行另一个 eBPF 并替换执行上下文,类似于一个进程执行 execve() 系统调用的方式。”
也就是说,尾调用之后,函数不会再返回给调用者了。
那么,eBPF 为什么要使用尾调用呢?这是因为,eBPF 的运行栈太有限了(仅有 512 字节),在递归调用函数时(实际上是向运行栈中一节一节地添加栈帧),很容易导致栈溢出。而尾调用恰恰允许在不增加堆栈的情况下,调用一系列函数。这是非常有效且实用的。
你可以使用下面的辅助函数来增加一个尾调用:
long bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)
其三个参数的含义分别是:
ctx 向被调用者传递当前 eBPF 程序的上下文信息。
prog_array_map 是一个程序数组(BPF_MAP_TYPE_PROG_ARRAY)类型的 eBPF map,用于记录一组 eBPF 程序的文件描述符。
index 为程序数组中需要调用的 eBPF 程序索引。
2 如何使用尾调用?
关于 BCC 框架,reference_guide.md 给出了一个例子。见 27.map.call()
内核态程序:
// example.c
BPF_PROG_ARRAY(prog_array, 10); // A)定义程序数组
int tail_call(void *ctx) {
bpf_trace_printk("Tail-call\n");
return 0;
}
int do_tail_call(void *ctx) {
bpf_trace_printk("Original program\n");
prog_array.call(ctx, 2); // B)调用 ID 为 2 的函数
return 0;
}
用户态程序:
b = BPF(src_file="example.c")
tail_fn = b.load_func("tail_call", BPF.KPROBE) # C)尾调用函数定义
prog_array = b.get_table("prog_array")
prog_array[c_int(2)] = c_int(tail_fn.fd) # D)绑定尾调用函数
b.attach_kprobe(event="some_kpr