eBPF Hello World
eBPF 是什么?
eBPF(extended Berkeley Packet Filter) 是一种可以在 Linux 内核中运行用户编写的程序,而不需要修改内核代码或加载内核模块的技术。
简单说,eBPF 让 Linux 内核变得可编程化了。
eBPF 程序是一个事件驱动模型。Linux 内核提供了各种 hook point,比如 system calls, function entry/exit, kernel tracepoints, network events 等。
eBPF 程序通过实现想要关注的 hook point 的 callback,并把这些 callback 注册到相应的 hook point 来完成“内核编程”。
eBPF 程序的执行流程
首先,是要编写和编译 eBPF 程序。eBPF 程序采用 C 语言编写,并可以通过 clang 编译成目标文件 —— eBPF 字节码。
然后,需要有一个应用程序通过调用 Linux 内核的系统调用,将编译好的 eBPF 字节码加载到内核中。在加载的过程中,内核会对 eBPF 程序进行验证,保证 eBPF 程序是安全的;然后将 eBPF 字节码编译成机器码。之后,内核会按照绑定好的 hook point 调用相应的函数。
上文是转载自:https://zhuanlan.zhihu.com/p/378258986 作者对 eBPF 解释的特别通俗易懂。
测试 eBPF 程序
eBPF 程序说实话挺难写的。入门阶段也不需要写,先学习,看看别人是怎么写的,后面再照葫芦画瓢。这里要介绍一个项目叫 BCC,接触 eBPF 的朋友都绕不开这个项目。
BCC
BCC 是 BPF Compiler Collection 的缩写。BCC 是一个用于创建 BPF 程序的工具集合,包括了一系列的工具和库,可以用来创建、加载、运行 eBPF 程序。BCC 的 github 地址:https://github.com/iovisor/bcc,认真读一下 README。
在 BCC 代码仓库的 tools 目录 下有一堆写好的 eBPF 工具,都可以作为 eBPF 编程参考。下面我们来安装尝试一下。
安装 BCC
我的测试环境是在腾讯云开的一个虚拟机,操作系统是:Debian 11.1。
root@VM-16-7-debian:/usr/share/bcc/examples# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
建议大家和我采用相同的环境,要不然可能会因为环境导致实验结果不一致。
# 使用 root 账号执行下面的命令
# 安装基础库
apt install -y bison build-essential cmake flex git libedit-dev libllvm11 llvm-11-dev libclang-11-dev zlib1g-dev libelf-dev libfl-dev python3-distutils zip
# 下载 bcc 最新的包,写这个文章的时候最新是 v0.28.0
wget https://github.com/iovisor/bcc/releases/download/v0.28.0/bcc-src-with-submodule.tar.gz
# 解压缩刚下载的包
tar zxvf bcc-src-with-submodule.tar.gz
# 在解压之后的 bcc 目录下创建 build 目录并进入 build 目录
cd bcc
mkdir build
cd build
# 编译安装
cmake -DPYTHON_CMD=python3 ..
make && make install
编译安装完成之后,在 /usr/share/bcc/tools 这个目录下会看到很多命令行工具,都是 Python 写的,我们就找这里的工具做测试和研究。在 /usr/share/bcc/examples 下面则有很多 examples,也可以做参考。
测试 hello world
在 examples 目录下有个 hello_world.py,我来执行一下看看:
root@VM-16-7-debian:/usr/share/bcc/examples# python3 hello_world.py
b' barad_agent-3736 [001] d... 2312.521289: bpf_trace_printk: Hello, World!'
b''
b' barad_agent-18310 [000] d... 2312.522550: bpf_trace_printk: Hello, World!'
b''
b' sh-18311 [000] d... 2312.524834: bpf_trace_printk: Hello, World!'
b''
b' sh-18311 [000] d... 2312.524943: bpf_trace_printk: Hello, World!'
b''
b' sh-18311 [000] d... 2312.525030: bpf_trace_printk: Hello, World!'
b''
b' barad_agent-3736 [000] d... 2314.521527: bpf_trace_printk: Hello, World!'
看起来只是打印了一些 Hello World 的日志,咱们看看这个文件的内容:
#!/usr/bin/python
# Copyright (c) PLUMgrid, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
# run in project examples directory with:
# sudo ./hello_world.py"
# see trace_fields.py for a longer example
from bcc import BPF
# This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
首先从 bcc 这个 Python 模块导入了 BPF 类,然后实例化了 BPF 类得到一个对象,然后调用这个对象的 trace_print 方法,完活。实例化 BPF 类的时候,传入了一个 text 参数,这个参数是一个字符串,里面是一个 eBPF 程序,这个程序的作用是在系统调用 sys_clone 的时候打印一条日志。
text 这个参数对应的这个字符串,是用 c 语言写的一段代码,这段代码里用到了 bpf_trace_printk 这样的方法,这个方法显然不是 c 语言内置的,而是 bcc 提供的,上面也没有啥 include 头文件之类的,底层肯定做了一些不为人知的事情,这里就不深究了。
上面的例子的挂载点是 sys_clone,大家可以去 /usr/share/bcc/tools 下找一个 oomkill 的脚本看看,会对 eBPF 有稍微多一点了解,这个 oomkill 的脚本是 Brendan Gregg,Brendan Gregg 是在 NetFlix 做性能优化的大神,作为后端技术人员大概率会知道他,容我们膜拜大神 3 秒钟。
总结
本文只是 eBPF 的小小入门,希望对大家有帮助。大家可以关注我的公众号:SRETalk,我会持续更新 eBPF 相关的文章。另外,我们快猫技术团队提供企业级可观测性产品解决方案,帮助您快速发现问题、定位故障,提升运维效率,欢迎咨询。
扩展阅读: