之前尝试了两种方法使用ePBF对内核数据进行提取和分析,第一个是bcc,具体信息见我的另一个blog:eBPF初体验_mozart1756的博客-CSDN博客。
再复习一下大致的步骤:
首先,确认内核包含如下编译选项:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
# [optional, for tc filters]
CONFIG_NET_CLS_BPF=m
# [optional, for tc actions]
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
# [for Linux kernel versions 4.1 through 4.6]
CONFIG_HAVE_BPF_JIT=y
# [for Linux kernel versions 4.7 and later]
CONFIG_HAVE_EBPF_JIT=y
# [optional, for kprobes]
CONFIG_BPF_EVENTS=y
# Need kernel headers through /sys/kernel/kheaders.tar.xz
CONFIG_IKHEADERS=y
这样保证内核是一个支持ebpf的内核,否则无论什么形式的ebpf程序,都无法在该内核上正常运行。
第二,安装必要的编译工具,至少要安装clang和llvm,我目前安装的版本是clang-12,同时,bcc是在python下运行,还有安装python。
最后,准备bcc的代码和运行环境:
git clone https://github.com/iovisor/bcc.git
mkdir bcc/build; cd bcc/build
cmake ..
make
sudo make install
cmake -DPYTHON_CMD=python3 .. # build python3 binding
pushd src/python/
make
sudo make install
popd
最后,看一下运行的结果(以biolatency.py为例):
sudo python3 biolatency.py
Tracing block device I/O... Hit Ctrl-C to end.
^C
usecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 3 |** |
64 -> 127 : 15 |*********** |
128 -> 255 : 50 |************************************* |
256 -> 511 : 54 |****************************************|
512 -> 1023 : 49 |************************************ |
1024 -> 2047 : 3 |** |
2048 -> 4095 : 4 |** |
4096 -> 8191 : 33 |************************ |
8192 -> 16383 : 2 |* |
bcc的有点是操作简单,容易上手,而且,由于bcc是在待测的内核上编译和运行,所以没有内核跨版本的兼容性问题。缺点也是由于由于bcc是在待测的内核上编译和运行,会消耗很多的系统资源,在一些场景下并不适用。
我对ebpf的第二个尝试是适用C代码直接在内核代码树中编写ebpf程序,代码的位置在内核代码的sample/bpf目录下,你需要把你的代码放到这个目录下,每个工具应该包括两个c文件,一个运行在内核空间,另一个运行在用户空间,然后在Makefile中添加相应的信息,最后运行
make M=sample/bpf编译即可。
我做了5个实时内核相关的小工具,包括:
这种方式的好处是比较简单,对于C语言比较友好,且仅需编译一次,运行时资源消耗较少。缺点是兼容性不好,内核版本更换之后,工具无法正常使用,需要移植和适配。
既然说到了兼容性问题,那内核的兼容性问题是什么的?ebpf工具大部分是依赖于内核的数据结构的,比如我从task_struct这个结构中读取pid这个字段,在4.19内核中,这个字段在task_struct
offset 8 地址,在5.4内核,也许会由于版本的演进,前面多了16个自己的字段,那么pid就变成task_struct
offset 24(这是个例子,pid在这两个版本之间并没有变换位置)。或者,某些字段的名字变了,比如thread_struct
的 fs
字段(获取 thread-local storage 用), 在 4.6 到 4.7 内核升级时就被重命名为了 fsbase
。
这些问题,都导致了ebpf的兼容性和移植性的问题。
目前社区致力于使用BTF和libbpf来达到CO-RE(compile once -- run everywhere)的效果,其中BTF在内核编译的时候记录了内核的调试信息,比如内核的结构,字段,偏移量等信息,同时,这些信息内核会输出到/sys/kernel/btf/vmlinux中,clang在编译ebpf的程序时,会对ebpf程序所使用的内核结构字段做一个记录,比如如果想访问 task_struct->pid,那clang 将做如下记录:这是一个位于结构体 struct task_struct 中、类型为 pid_t、名为 pid 的字段。libbpf会查看 ebpf 程序记录的 BTF 的重定位信息,对ebpf中使用内核数据的部分进行地址的重定位。这个就是CO-RE解决兼容和移植问题的方法。具体的细节描述见下面两篇blog:
BPF 可移植性和 CO-RE(一次编译,到处运行)_米开朗基杨的博客-CSDN博客
BPF BTF 详细介绍_dwh0403的博客-CSDN博客_bpf是什么文件格式
在bcc中,由一个目录为libbpf-tools,就是bcc使用CO-RE来使用C语言编写可移植的ebpf程序,我对此做了一些尝试:
1,获取最新的bcc代码
git clone https://github.com/iovisor/bcc.git
2,在bcc中获取libbpf和bpftool的代码
git submodule update --init --recursive
此时你再查看bcc/libbpf-tools/bpftool和bcc/src/cc/libbpf中也已经下载好了需要使用的源码。
回到bcc/libbpf-tools,运行make进行编译。
此时,我遇到了一个编译错误,没有函数__builtin_preserve_type_info的定义。
查阅了一些资料,找到__builtin_preserve_type_info是一个clang提供的builtin函数,需要至少clang12及以上的版本,我检查了一下我使用设备的clang的版本,发现我安装了两个clang,一个是clang10,另一个是clang-12,编译时使用的是clang10,所以没有__builtin_preserve_type_info的定义。
使用如下命令切换clang的版本:
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-12 100 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-12
编译成功,运行其中一个工具试一下,发现如下错误:
libbpf: failed to find valid kernel BTF
libbpf: Error loading vmlinux BTF: -3
libbpf: failed to load object 'wakeuptime_bpf'
libbpf: failed to load BPF skeleton 'wakeuptime_bpf': -3
failed to load BPF object: -3
这个是由于当前的内核在编译的时候没有支持BTF,所以需要重新配置和编译内核:
make menuconfig
选中kernel hacking --> Compile-time checks and compiler option -->Generate BTF typeinfo后,打开了编译开关CONFIG_DEBUG_INFO_BTF。
然后重新编译内核,又遇到了问题:
LD .tmp_vmlinux.btf
BTF .btf.vmlinux.bin.o
Killed
LD .tmp_vmlinux.kallsyms1
KSYMS .tmp_vmlinux.kallsyms1.S
AS .tmp_vmlinux.kallsyms1.S
LD .tmp_vmlinux.kallsyms2
KSYMS .tmp_vmlinux.kallsyms2.S
AS .tmp_vmlinux.kallsyms2.S
LD vmlinux
BTFIDS vmlinux
FAILED: load BTF from vmlinux: No such file or directory
make: *** [Makefile:1169:vmlinux] 错误 255
make: *** 正在删除文件“vmlinux”
不能正常的生成带有BTF信息的内核,这个问题困扰了我很久,百度也查询不到,后来我使用bing,查到了一些信息,生成BTF信息所使用的工具pahole有可能在使用中出了一些故障,我通过命令“dmesg|grep pahole”查了一下,发现它被oom killer杀掉了,而且根据dmesg信息,pahole确实消耗了很多的物理内存。
我关掉了一些正在使用的应用,重新编译,通过。
或者也可以使用如下命令,在编译内核时出现信息“BTF .btf.vmlinux.bin.o”的时候,用如下命令提高pahole的oom score。
sudo su
ps -aux|grep pahole
echo -17 >/proc/pahole_pid/oom_adj
最后终于编译成功了,安装,重启,使用新的内核再运行一下libbpf-tools中的工具:
sudo ./softirqs
[sudo] kylin 的密码:
Tracing soft irq event time... Hit Ctrl-C to end.
^C
SOFTIRQ TOTAL_usecs
hi 505
timer 4425
net_rx 1021
tasklet 36
sched 18034
rcu 3407
成功。