BCC和libbpf的转换

历史上,当需要开发一个BPF应用时可以选择BCC 框架,在实现各种用于Tracepoints的BPF程序时需要将BPF程序加载到内核中。BCC提供了内置的Clang编译器,可以在运行时编译BPF代码,并将其定制为符合特定主机内核的程序。这是在不断变化的内核内部下开发可维护的BPF应用程序的唯一方法。在BPF的可移植性和CO-RE一文中详细介绍了为什么会这样,以及为什么BCC是之前唯一的可行方式,此外还解释了为什么 libbpf是目前比较好的选择。去年,Libbpf的功能和复杂性得到了重大提升,消除了与BCC之间的很多差异(特别是对Tracepoints应用来说),并增加了很多BCC不支持的新的且强大的特性(如全局变量和BPF skeletons)。

诚然,BCC会竭尽全力简化BPF开发人员的工作,但有时在获取便利性的同时也增加了问题定位和修复的困难度。用户必须记住其命名规范以及自动生成的用于Tracepoints的结构体,且必须依赖这些代码的重写来读取内核数据和获取kprobe参数。当使用BPF map时,需要编写一个半面向对象的C代码,这与内核中发生的情况并不完全匹配。除此之外,BCC使得用户在用户空间编写了大量样板代码,且需要手动配置最琐碎的部分。

如上所述,BCC依赖运行时编译,且本身嵌入了庞大的LLVM/Clang库,由于这些原因,BCC与理想的使用有一定差距:

编译时的高资源利用率(内存和CPU),在繁忙的服务器上时有可能干扰主流程。

依赖内核头文件包,不得不在每台目标主机上进行安装。即使这样,如果需要某些没有通过公共头文件暴露的内核内容时,需要将类型定义拷贝黏贴到BPF代码中,通过这种方式达成目的。

即使是很小的编译时错误也只能在运行时被检测到,之后不得不重新编译并重启用户层的应用;这大大影响了开发的迭代时间(并增加了挫败感...)

Libbpf + BPF CO-RE (Compile Once – Run Everywhere) 选择了一个不同的方式,其思想在于将BPF程序视为一个普通的用户空间的程序:仅需要将其编译成一些小的二进制,然后不用经过修改就可以部署到目的主机上。libbpf扮演了BPF程序的加载器,负责配置工作(重定位,加载和校验BPF程序,创建BPF maps,附加到BPF钩子上等),开发者仅需要关注BPF程序的正确性和性能即可。这种方式使得开销降到了最低,消除了大量依赖,提升了整体开发者的开发体验。

在API和代码约定方面,libbpf坚持"最少意外"的哲学,即大部分内容都需要明确地阐述:不会隐含任何头文件,也不会重写代码。仅使用简单的C代码和适当的辅助宏即可消除大部分单调的环节。 此外,用户编写的是需要执行的内容,BPF应用程序的结构是一对一的,最终由内核验证并执行。

本指南用于简单快速地将BCC转换为libbpf+BPF CO-RE。本文解释了多种预配置步骤,并概述了常见的模式,以及可能会碰到的不同点,困难和陷阱。

一开始将BCC转换为普通的BPF CO-RE时,可能会感到不适和困惑,但很快就会掌握它,并在下次遇到编译或验证问题时欣赏libbpf的明确性和直接性。

此外,注意BPF CO-RE用到的很多Clang特性都比较新,需要用到Clang 10或更新的版本

可以参照官方文档升级Clang:

git clone https://github.com/llvm/llvm-project.git

Build LLVM and Clang:

cd llvm-project

mkdir build (in-tree build is not supported)

cd build

cmake -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" ../llvm

make

注意:在2.3步执行cmake时,可能会因为Host GCC version must be at least 5.1,这样的错误,需要升级GCC,升级之后删除build再重新编译即可。但有时即便GCC升级成功,且清除build中的缓存,再次编译时还是会出现上述错误,可以手动指定GCC路径来解决该问题:

CC=$HOME/toolchains/bin/gcc cmake -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" ../llvm

另外就是在执行make命令时会执行lib库的编译和链接,在链接过程中会占用大量内存,建议在执行该命令时打开(或扩大)系统的swap功能,防止内存不足导致系统出问题。

配置用户空间 生成必要的内容

构建基于libbpf的BPF应用需要使用BPF CO-RE包含的几个步骤:

生成带所有内核类型的头文件vmlinux.h

使用Clang(版本10或更新版本)将BPF程序的源代码编译为.o对象文件

从编译好的BPF对象文件中生成BPF skeleton 头文件 (BPF skeleton 头文件内容来自上一步生成的.o文件,可以参考libbpf-tools的Makefile文件,可以看到 skeleton 头文件其实是通过bpftool gen命令生成的)

在用户空间代码中包含生成的BPF skeleton 头文件

最后,编译用户空间代码,这样会嵌入BPF对象代码,后续就不用发布单独的文件

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxgdy.html