eBPF学习计划可以看这里。 该篇为入门文章翻译系列第二篇,第一篇看这里。 原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/
目录
在本系列的前一篇文章中,我讨论了如何使用eBPF安全地运行内核内用户空间提供的代码。然而,对于新手来说,eBPF最大的挑战之一是编写程序需要编译并从内核源代码链接到eBPF库。内核开发人员可能总是可以获得内核源代码的副本,但是对于在生产环境机器或客户机器上工作的工程师来说,情况就不一样了。解决这个限制是创建BPF编译器集合的原因之一。该项目包括用于编写、编译和加载eBPF程序的工具链,以及用于调试和诊断性能问题的示例程序和久经考验的工具。
自2015年4月发布以来,许多开发人员都研究过BCC项目,其中113位贡献者已经一起创建并提供了令人印象深刻的工具集,包括100多个示例和随时可用的跟踪工具。例如,使用用户静态定义跟踪(USDT)探测的脚本(一种来自DTrace的在用户空间代码中放置跟踪点的机制),用于跟踪垃圾收集事件、方法调用和系统调用,以及高级语言中的线程创建和销毁。许多流行的应用程序,特别是数据库,也有USDT探测,可以通过配置开关(—-enable-dtrace
)启用它。这些探测,顾名思义,会在编译时,被静态地插入到用户应用程序中。在不久的将来,我将专门写一篇关于USDT探测的LWN文章。
项目文档展示了如何使用现有的脚本和工具进行全面的性能调查,而不需要编写一行代码,BCC项目库中提供了一个方便上手的教程。另一个有用指南是由Brendan Gregg撰写的,他对BCC和工具补丁的贡献数量排名第二(在撰写本文时,Sasha Goldshtein的贡献数量排名第一)。
在BCC中可以使用Python和Lua语言的作为入口进行编程。使用这些高级语言,可以编写短小但富有表现力的程序,同时具备C语言所缺少的全部数据操控的优势。例如,开发人员可以将eBPF map类比为Python字典,并可以直接访问映射内容,这是通过使用BPF帮助函数,它在内部实现这个功能。这有助于降低使用eBPF的潜在开发人员的门槛,因为他们可以使用处理数据惯用的标准模式。
BCC调用LLVM Clang编译器,这个编译器具有BPF后端,可以将C代码转换成eBPF字节码。然后,BCC负责使用bpf()系统调用函数,将eBPF字节码加载到内核中。如果加载失败,例如内核验证器检查失败,则BCC提供有关加载失败原因的提示,如,“提示:如果在没有首先检查指针是否为空的情况下,从map查找中取消引用指针值,可能就会出现The 'map_value_or_null'
”。这是创建BCC的另一个动机——因为很难写出明显正确的BPF程序;当你犯了错误时,BCC会通知你。
为了演示如何快速地开始使用BCC,下面是来自BCC项目的“Hello, World!”示例程序(译者注:必须使用root权限执行)。每次运行系统函数clone()时,它都会打印到跟踪缓冲区中。我稍微修改了一下格式,以便于阅读。
#!/usr/bin/env python
from bcc import BPF
program='''
int kprobe__sys_clone(void *ctx)
{
bpf_trace_printk("Hello, World!\n");
return 0;
}
'''
整个eBPF程序包含在program
变量中。它是运行内核里的eBPF虚拟机上的代码。函数名“kprobe__sys_clone()
”的格式很重要:kprobe__
前缀表示BCC工具链将一个kprobe附加到它后面的内核符号上。在这里,是sys_clone()
这个符号。当调用sys_clone()
时,这个kprobe会被触发,然后运行eBPF程序,bpf_trace_printk()
会打印“Hello, World!”到内核的跟踪缓冲区中。
以前比较繁琐的任务是,将程序编译为eBPF字节码,并将其加载到内核。现在完全只需通过实例化一个新的BPF对象就可以处理。所有低层次的工作都是在幕后完成的,就Python bindings和BCC的libbpf库中。
函数BPF.trace_print()
对内核的跟踪缓冲区文件执行阻塞读取,并将内容打印到标准输出中。这是输出内容:
gnome-terminal--3210 [003] d..2 19252.369014: 0x00000001: Hello, World!
gnome-terminal--3210 [003] d..2 19252.369080: 0x00000001: Hello, World!
pool-21543 [001] d..2 19252.382317: 0x00000001: Hello, World!
bash-21545 [002] d..2 19252.385535: 0x00000001: Hello, World!
bash-21546 [003] d..2 19252.385752: 0x00000001: Hello, World!
bash-21545 [002] d..2 19252.386883: 0x00000001: Hello, World!
输出内容格式说明如下:
[]
里面)最后一个字段就是我们传递给bpf_trace_printk()
的“Hello, World!”字符串。倒数第二个字段包含0x00000001
这个地址。通常情况下,当内核代码写入跟踪缓冲区时,系统指令trace_printk()
被调用后,这个指令的指针地址将打印在该字段中。不幸的是,并没有为bpf_trace_printk()
这个系统函数实现这个情况,所以总是使用硬编码的地址0x00000001
。
译者注: 为了更方便大家动手操作,提供了vagrant虚拟机环境,已安装bcc工具集合。 下载方式如下所示:链接: https://pan.baidu.com/s/11dsEU6Yk6KGDGNor-fbsgQ 提取码: qvhc。 使用方式如下所示:
# download the box locally
> vagrant init [ur-box-name] [the-path-where-ur-box-is-located]
> vagrant up
# BCC base dir is `/usr/share/bcc`
# BCC tool collection is in `/usr/share/bcc/tools`
# You can find the examples in `/usr/share/bcc/examples`
为大家录制了 Hello World 操作视频。
argdist.py这个文件将探针(uprobe、kprobe、tracepoint或USDT)插入到给定的函数中,该函数可以位于内核中,也可以位于用户空间代码中。当探针被触发时,argdist.py
会打印函数的参数值,以计数器或直方图的形式显示。它会一直运行,直到被用户中断。例如,下面的命令输出调用irq_handler_entry()
的次数,以及引发中断的次数。
$ tools/argdist.py -C 't:irq:irq_handler_entry():int:args->irq'
[14:14:24]
t:irq:irq_handler_entry():int:args->irq
COUNT EVENT
12 args->irq = 45
16 args->irq = 53
52 args->irq = 48
[14:14:25]
t:irq:irq_handler_entry():int:args->irq
COUNT EVENT
1 args->irq = 49
5 args->irq = 53
24 args->irq = 45
由于histogram选项(-H
)使用桶将多个中断分组在一起,所以在这种情况下,它不如count选项(-C
)有用。但是,直方图输出很有帮助的一个场景,是使用btrfsdist.py工具,该工具将Btrfs的读、写、打开和fsync操作的延迟,汇总到power-of-two桶中。
$ tools/btrfsdist.py
Tracing btrfs operation latency... Hit Ctrl-C to end.
^C
operation = 'read'
usecs : count distribution
0 -> 1 : 775 |****************************************|
2 -> 3 : 60 |*** |
4 -> 7 : 20 |* |
8 -> 15 : 3 | |
16 -> 31 : 3 | |
32 -> 63 : 0 | |
64 -> 127 : 0 | |
128 -> 255 : 1 | |
256 -> 511 : 19 | |
512 -> 1023 : 12 | |
operation = 'write'
usecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 2 |********** |
4 -> 7 : 8 |****************************************|
8 -> 15 : 1 |***** |
16 -> 31 : 4 |******************** |
32 -> 63 : 4 |******************** |
operation = 'open'
usecs : count distribution
0 -> 1 : 636 |************************************