eBPF原理介绍与编程实践
注:本文包括了ebpf的原理介绍、流程分析、相关资料链接、工具编写实战等,可以选择感兴趣的部分直接阅读;鉴于作者语文水平有限,很多地方描述可能不清楚,有错误或疑问欢迎指出交流
1. 初步了解
1.1 eBPF
eBPF是一个用RISC指令集设计的VM,他可以通过运行BPF程序来跟踪内核函数、内存等。
用Linux社区大牛Gregg的话来讲,“eBPF does to Linux what JavaScript does to HTML”。
1.2 BCC
BCC,全名BPF Compiler Collection,在 github.com/iovisor/bcc, 这个工具集提供了很多样例的tracing工具可以直接使用,同时也提供了可用于开发这些工具的python、lua接口。
安装步骤: https://github.com/iovisor/bcc/blob/master/INSTALL.md
BCC工具教程: https://github.com/iovisor/bcc
BCC python教程: https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
BCC reference: https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
1.3 bpftrace
bpftrace 是一组bcc的封装工具,比起bcc是面向一些更复杂、庞大的问题而言,bpftrace进一步封装,调用了bcc接口来实现了通过一行脚本来定位一些特定场景下的问题。
地址github.com/iovisor/bpftrace,
安装步骤: https://github.com/iovisor/bpftrace/blob/master/INSTALL.md
Bpftrace编程教程: https://github.com/iovisor/bpftrace/blob/master/docs/tutorial_one_liners.md
Bpftrace reference: https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
2. eBPF 原理
注: 以下全部代码基于Linux 5.8
2.1 工作流程
编译器把C代码编译成eBPF字节码(这里有点像jvm机制,只不过这个ebpf的vm在内核)。
用户态程序把字节码、程序类型发给内核,以此来决定哪些内核区域(代码、内存)可以被这个程序访问(通过调用 bpf_prog_alloc 然后把字节码复制到 prog->insns, 见图2.1.1 bpf_prog struct)。
内核对字节码做一个检查,保证这个程序是安全的(没有越界、死循环等等),代码在kernel/bpf/verifier.c。
内核通过JIT编译字节码到机器码,然后通过kprobe、tracepoint来把这些代码插入到对应的位置。
这些被插入的bpf程序代码会把数据写到他们自己的ringbuffers或者key-value maps(后面会讲到,ebpf存储数据的自定义内核数据结构)。
用户态读取这些ringbuffers或者maps来获取想要的数据。
通过查看 /sys/kernel/debug/tracing/events/*moudle_name*/*tracepoint_name* 去看有哪些支持的tracepoint。
因此在本例中,需要查看block相关的tracepoint,可以找到tracepoint block_rq_issue,而而且我们可以通过/sys/kernel/debug/tracing/events/block/block_rq_issue/format 了解参数格式,见图3.2.3。
在图3.2.3中可以看到一个bytes域,如果需要记录bytes,可以直接在我们的程序中去使用它。
在此之前,需要先想办法保存他,最简单直接的方法是自己定义一个map去存储,但是从前面ebpf原理的分析我们知道map是分离的,如果新搞一个会很影响性能,因此我们需要重写当前的map, 见图3.2.4.
现在我们需要获取bytes(在 TRACEPOINT_PROBE 中是通过 args->*在format文件中的参数名* 获取) 来更新map,还有一点需要注意的是我们使用 dev(id) + sector(id) 来作为 key (这个是来自Gregg’s 对写IO requests相关工具开发者的建议), 见图 3.2.5.
对于 block_rq_complete 写法非常类似,见图 3.2.6.
现在ebpf部分程序已经改完了,最后我们需要修改一下python部分来增加一下新增的输出,这里我们仅需要将输出的trace_point的最后一个域调用split,第一部分打印我们刚加的bytes,第二部分打印原来的耗时。(因为这里是顺序打印, 可以参考图3.2.6重的bpf_trace_printk ), python部分见图 3.2.7.
这里对 disksnoop.py的改动已经完成了,在本例中其实改动非常简单,仅起到抛砖引玉的作用,对于更多的其他功能,需要研究有没有相关的tracepoint,如果没有就只能做动态trace了。
4 参考文献
http://www.brendangregg.com/blog/2019-01-01/learn-ebpf-tracing.html
http://www.brendangregg.com/ebpf.html
https://github.com/iovisor/bcc/blob/master/docs/tutorial.md
https://github.com/iovisor/bcc/blob/master/docs/reference_guide
https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/
https://blog.csdn.net/hjkfcz/article/details/104916719
https://blog.yadutaf.fr/2016/03/30/turn-any-syscall-into-event-introducing-ebpf-kernel-probes/