eBPF文章翻译(2)——BCC介绍(附实验环境)
eBPF学习计划可以看这里。
该篇为入门文章翻译系列第二篇,第一篇看这里。
原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/
目录
BCC是什么
在本系列的前一篇文章中,我讨论了如何使用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会通知你。
一个非常快速的”Hello, World“示例
为了演示如何快速地开始使用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!
输出内容格式说明如下:
- kprobe触发时正在运行的应用程序名称
- 这个应用程序的PID
- 运行在哪个CPU核心上(在
[]
里面) - 各种进程上下文标志
- 时间戳
最后一个字段就是我们传递给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 |****************************************| 2 -> 3 : 22 |* | 4 -> 7 : 16 |* | 8 -> 15 : 2 | | 16 -> 31 : 1 | | operation = 'fsync' usecs : count distribution 0 -> 1 : 0 | | 2 -> 3 : 0 | | 4 -> 7 : 0 | | 8 -> 15 : 0 | | 16 -> 31 : 0 | | 32 -> 63 : 0 | | 64 -> 127 : 0 | | 128 -> 255 : 0 | | 256 -> 511 : 0 | | 512 -> 1023 : 0 | | 1024 -> 2047 : 0 | | 2048 -> 4095 : 0 | | 4096 -> 8191 : 1 |****************************************|
未来还会有更多
这是对bbc的简短介绍。在下一篇文章中,我们将探索一些更复杂的主题,比如如何访问eBPF数据结构,如何配置eBPF程序的编译方式,以及如何调试程序,所有这些都使用Python语言作为入口。
1 对 “eBPF文章翻译(2)——BCC介绍(附实验环境)”的想法;