一文看懂eBPF|eBPF的简单使用
eBPF(extended Berkeley Packet Filter)可谓 Linux 社区的新宠,很多大公司都开始投身于eBPF技术,如 Goole、Facebook、Twitter 等。
eBPF 究竟有什么魅力让大家都关注它呢?
这是因为 eBPF 增加了内核的可扩展性,让内核变得更加灵活和强大。
如果大家玩过乐高积木的话就会深有体会,乐高积木就是通过不断向主体添加积木来组合出更庞大的模型。
而 eBPF 就像乐高积木一样,可以不断向内核添加 eBPF 模块来增强内核的功能。
本文分为3篇:
eBPF 的简单使用eBPF 的实现原理kprobes 在 eBPF 中的实现原理看完这3篇文章,估计对 eBPF 也有较深的理解了。
什么是 eBPFeBPF 全称 extended Berkeley Packet Filter,中文意思是扩展的伯克利包过滤器。一般来说,要向内核添加新功能,需要修改内核源代码或者编写内核模块来实现。而 eBPF 允许程序在不修改内核源代码,或添加额外的内核模块情况下运行。
从 eBPF 的名字看,好像是专门为过滤网络包而创造的。其实,eBPF 是从 BPF(也称为 cBPF:classic Berkeley Packet Filter)发展而来的,BPF 是专门为过滤网络数据包而创造的。
但随着 eBPF 不断完善和加强,现在的 eBPF 已经不再限于过滤网络数据包了。
eBPF 架构我们先来看看 eBPF 的架构,如下图所示:
下面用文字来描述一下:
用户态
用户编写 eBPF 程序,可以使用 eBPF 汇编或者 eBPF 特有的 C 语言来编写。使用 LLVM/CLang 编译器,将 eBPF 程序编译成 eBPF 字节码。调用bpf()系统调用把 eBPF 字节码加载到内核。内核态
当用户调用bpf()系统调用把 eBPF 字节码加载到内核时,内核先会对 eBPF 字节码进行安全验证。使用JIT(Just In Time)技术将 eBPF 字节编译成本地机器码(Native Code)。然后根据 eBPF 程序的功能,将 eBPF 机器码挂载到内核的不同运行路径上(如用于跟踪内核运行状态的 eBPF 程序将会挂载在kprobes的运行路径上)。当内核运行到这些路径时,就会触发执行相应路径上的 eBPF 机器码。如果大家使用过 Java 编写程序的话,会发现 eBPF 与 Java 的AOP(Aspect Oriented Programming 面向切面编程)概念很像。
为了让有 Java 经验的同学更容易接受 eBPF 技术。我们先介绍一下 Java 中的 AOP 概念。
在 AOP 概念中,有两个很重要的角色:切点和拦截器。
切点:程序中某个具体的业务点(方法)。拦截器:拦截器其实是一段 Java 代码,用于拦截切点在执行前(或执行后),先运行这段 Java 代码。eBPF 程序就像 AOP 中的拦截器,而内核的某个运行路径就像 AOP 中的切点。
根据挂载点功能的不同,大概可以分为以下几个模块:
性能跟踪网络容器安全eBPF 使用在介绍 eBPF 的实现前,我们先来介绍一下如何使用 eBPF 来跟踪fork()系统调用的运行情况。
编写 eBPF 程序有多种方式,比如使用原生 eBPF 汇编来编写,但使用原生 eBPF 汇编编写程序的难度较大,所以一般不建议。
也可以使用 eBPF 受限的 C 语言来编写,难度比使用原生 eBPF 汇编简单些,但对初学者来说也不是十分友好。
最简单是使用 BCC 工具来编写,BCC 工具帮我们简化了很多繁琐的工作,比如不用编写加载器。
下面我们将使用 BCC 工具来介绍怎么编写一个 eBPF 程序。
1. BCC 工具安装注意:由于 eBPF 对内核的版本有较高的要求,不同版本的内核对 eBPF 的支持可能有所不相同。所以使用 eBPF 时,最好使用最新版本的内核。
本文使用Ubuntu 20.20(内核版本为5.8.1)作为解说。
在 Ubuntu 系统中安装 BCC 工具是比较简单的,可以使用以下命令:
$sudoapt-getinstallbpfcc-toolslinux-headers-$(uname-r)
BCC 工具可以让你使用 Python 和 C 语言组合来编写 eBPF 程序。
安装完成后,可以使用命令bcc -v来测试是否安装成功。如果安装失败,可以参考官网安装文档,如下:
2. 编写 eBPF 版的 hello worldhttps://github.com/iovisor/bcc/blob/master/INSTALL.md
一般编程课的第一步都是编写著名的hello world程序,所以我们也以编写hello world程序作为第一步吧。
使用 BCC 编写 eBPF 程序的步骤如下:
使用 C 语言编写 eBPF 程序的内核态功能(也就是运行在内核态的 eBPF 程序)。使用 Python 编写加载代码和用户态功能。为什么不能全部使用 Python 编写呢?这是因为 LLVM/Clang 只支持将 C 语言编译成 eBPF 字节码,而不支持将 Python 代码编译成 eBPF 字节码。
所以,eBPF 内核态程序只能使用 C 语言编写。而 eBPF 的用户态程序可以使用 Python 进行编写,这样就能简化编写难度。
所以,第一步就是编写 eBPF 内核态程序。
使用 C 编写 eBPF 程序
新建一个 hello.c 文件,并输入下面的内容:
inthello_world(void*ctx){bpf_trace_printk("Hello,World!");return0;}
使用 Python 和 BCC 工具开发一个用户态程序
新建一个 hello.py 文件,并输入下面的内容:
#!/usr/bin/envpython3#1)加载BCC库frombccimportBPF#2)加载eBPF内核态程序b=BPF(src_file="hello.c")#3)将eBPF程序挂载到kprobeb.attach_kprobe(event="do_sys_openat2",fn_name="hello_world")#4)读取并且打印eBPF内核态程序输出的数据b.trace_print()
下面我们来看看每一行代码的具体含义:
导入了 BCC 库的 BPF 模块,以便接下来调用。调用 BPF() 函数加载 eBPF 内核态程序(也就是我们编写的hello.c)。将 eBPF 程序挂载到内核探针(简称 kprobe),其中do_sys_openat2()是系统调用openat()在内核中的实现。读取内核调试文件/sys/kernel/debug/tracing/trace_pipe的内容(bpf_trace_printk()函数会将信息写入到此文件),并打印到标准输出中。运行 eBPF 程序
用户态程序开发完成之后,最后一步就是执行它了。需要注意的是,eBPF 程序需要以root用户来运行:
$sudopython3hello.py
运行后,可以看到如下输出:
$sudopython3hello.pyb"python3-31683[001]....614653.225903:0:Hello,World!"b"python3-31683[001]....614653.226093:0:Hello,World!"b"python3-31683[001]....614653.226606:0:Hello,World!"b"<...>-31684[000]....614654.387288:0:Hello,World!"b"irqbalance-669[000]....614658.232433:0:Hello,World!"...
到了这里,我们已经成功开发并运行了第一个 eBPF 程序。当然,这个程序很简单,并且也没有实际的用途。
但通过这个程序,我们大概可以知道使用 BCC 开发一个 eBPF 程序的步骤。
因为本系列文章并不是介绍如何开发 eBPF 程序,而是介绍 eBPF 的原理和实现。如果大家有兴趣学习如何开发 eBPF 程序,那么建议大家看看《BPF性能之巅》这本书,这本书详细地介绍了如何开发 eBPF 程序。
在下篇文章中,我们将介绍 eBPF 的实现原理,敬请期待。
相关阅读
-
世界热推荐:今晚7:00直播丨下一个突破...
今晚19:00,Cocos视频号直播马上点击【预约】啦↓↓↓在运营了三年... -
NFT周刊|Magic Eden宣布支持Polygon网...
Block-986在NFT这样的市场,每周都会有相当多项目起起伏伏。在过去... -
环球今亮点!头条观察 | DeFi的兴衰与...
在比特币得到机构关注之后,许多财务专家预测世界将因为加密货币的... -
重新审视合作,体育Crypto的可靠关系才能双赢
Block-987即使在体育Crypto领域,人们的目光仍然集中在FTX上。随着... -
简讯:前端单元测试,更进一步
前端测试@2022如果从2014年Jest的第一个版本发布开始计算,前端开发... -
焦点热讯:刘强东这波操作秀
近日,刘强东发布京东全员信,信中提到:自2023年1月1日起,逐步为...