~ 更新于 2017-11-02 ~
BPF,及 伯克利包过滤器 B erkeley P acket F ilter ,最初构想提出于 1992 年,其目的是为了提供一种过滤包的方法,并且要避免从内核空间到用户空间的无用的数据包复制行为。它最初是由从用户空间注入到内核的一个简单的字节码构成,它在那个位置利用一个校验器进行检查 —— 以避免内核崩溃或者安全问题 —— 并附着到一个套接字上,接着在每个接收到的包上运行。几年后它被移植到 Linux 上,并且应用于一小部分应用程序上(例如,tcpdump
)。其简化的语言以及存在于内核中的即时编译器(JIT),使 BPF 成为一个性能卓越的工具。
然后,在 2013 年,Alexei Starovoitov 对 BPF 进行彻底地改造,并增加了新的功能,改善了它的性能。这个新版本被命名为 eBPF (意思是 “extended BPF”),与此同时,将以前的 BPF 变成 cBPF(意思是 “classic” BPF)。新版本出现了如映射和 尾调用 tail call 这样的新特性,并且 JIT 编译器也被重写了。新的语言比 cBPF 更接近于原生机器语言。并且,在内核中创建了新的附着点。
感谢那些新的钩子,eBPF 程序才可以被设计用于各种各样的情形下,其分为两个应用领域。其中一个应用领域是内核跟踪和事件监控。BPF 程序可以被附着到探针(kprobe),而且它与其它跟踪模式相比,有很多的优点(有时也有一些缺点)。
另外一个应用领域是网络编程。除了套接字过滤器外,eBPF 程序还可以附加到 tc(Linux 流量控制工具)的入站或者出站接口上,以一种很高效的方式去执行各种包处理任务。这种使用方式在这个领域开创了一个新的天地。
并且 eBPF 通过使用为 IO Visor 项目开发的技术,使它的性能进一步得到提升:也为 XDP(“eXpress Data Path”)添加了新的钩子,XDP 是不久前添加到内核中的一种新式快速路径。XDP 与 Linux 栈组合,然后使用 BPF ,使包处理的速度更快。
甚至一些项目,如 P4、Open vSwitch,考虑 或者开始去接洽使用 BPF。其它的一些,如 CETH、Cilium,则是完全基于它的。BPF 是如此流行,因此,我们可以预计,不久之后,将围绕它有更多工具和项目出现 …
就像我一样:我的一些工作(包括 BEBA)是非常依赖 eBPF 的,并且在这个网站上以后的几篇文章将关注于这个主题。按理说,在深入到细节之前,我应该以某种方式去介绍 BPF —— 我的意思是,真正的介绍,在第一节所提供的简要介绍上更多地介绍在 BPF 上开发的新功能:什么是 BPF 映射?尾调用?内部结构是什么样子?等等。但是,在这个网站上已经有很多这个主题的介绍了,而且,我也不希望去写另一篇 “BPF 介绍” 的重复文章。
毕竟,我花费了很多的时间去阅读和学习关于 BPF 的知识,因此,在这里我们将要做什么呢,我收集了非常多的关于 BPF 的阅读材料:介绍、文档,也有教程或者示例。这里有很多的材料可以去阅读,但是,为了去阅读它,首先要去 找到 它。因此,为了能够帮助更多想去学习和使用 BPF 的人,现在的这篇文章给出了一个资源清单。这里有各种阅读材料,它可以帮你深入理解内核字节码的机制。
这篇文章中下面的链接提供了 BPF 的基本概述,或者,一些与它密切相关的一些主题。如果你对 BPF 非常陌生,你可以在这些介绍文章中挑选出一篇你喜欢的文章去阅读。如果你已经理解了 BPF,你可以针对特定的主题去阅读,下面是阅读清单。
关于 eBPF 的常规介绍:
BPF 内部结构:
IO Visor 博客 有一些关于 BPF 的值得关注技术文章。它们中的一些包含了一点营销讨论。
内核跟踪:总结了所有的已有的方法,包括 BPF:
关于 事件跟踪和监视,Brendan Gregg 大量使用了 eBPF,并且就其使用 eBPFR 的一些案例写了极好的文档。如果你正在做一些内核跟踪方面的工作,你应该去看一下他的关于 eBPF 和火焰图相关的博客文章。其中的大多数都可以 从这篇文章中 访问,或者浏览他的博客。
介绍 BPF,也介绍 Linux 网络的一般概念:
硬件 卸载 offload (LCTT 译注:“卸载”是指原本由软件来处理的一些操作交由硬件来完成,以提升吞吐量,降低 CPU 负荷。):
关于 cBPF:
xt_bpf
模块一起使用 BPF 字节码。值得一提的是,从 Linux 内核 4.10 开始,eBPF 也是通过这个模块支持的。(虽然,我并不知道关于这件事的任何讨论或者文章)在 IO Visor 网站上的 XDP 概述。
eXpress Data Path (XDP) (Tom Herbert, Alexei Starovoitov, March 2016): 这是第一个关于 XDP 的演讲。
BoF - BPF 能为你做什么? (Brenden Blanco, LinuxCon, Toronto, August 2016)。
eXpress Data Path (Brenden Blanco, Linux Meetup at Santa Clara, July 2016): 包含一些(有点营销的意思?)基准测试结果!使用单一核心:
Jesper Dangaard Brouer 有几个非常好的幻灯片,它可以从本质上去理解 XDP 的内部结构。
(Jesper 也创建了并且尝试去扩展了有关 eBPF 和 XDP 的一些文档,查看 相关节。)
一旦你对 BPF 是做什么的有一个大体的理解。你可以抛开一般的演讲而深入到文档中了。下面是 BPF 的规范和功能的最全面的文档,按你的需要挑一个开始阅读吧!
bpf()
系统调用,它用于从用户空间去管理 BPF 程序和映射。它也包含一个 BPF 高级特性的描述(程序类型、映射等等)。第二个是主要是处理希望附加到 tc 接口的 BPF 程序:它是 tc-bpf(8) man 页面,是 使用 BPF 和 tc 的一个参考,并且包含一些示例命令和参考代码。当为了网络目的结合使用 BPF 与 tc (Linux 流量控制 t raffic c ontrol 工具)时,它可用于收集 tc 的常规功能的信息。这里有几个关于它的资源。
tc
较多,这里有一些好消息:我用这个工具 写了一个 bash 补完功能,并且它被包 iproute2 带到内核版本 4.6 和更高版中!P4 是一个用于指定交换机行为的语言。它可以为多种目标硬件或软件编译。因此,你可能猜到了,这些目标中的一个就是 BPF … 仅部分支持的:一些 P4 特性并不能被转化到 BPF 中,并且,用类似的方法,BPF 可以做的事情,而使用 P4 却不能表达出现。不过,P4 与 BPF 使用 的相关文档,被隐藏在 bcc 仓库中。这个改变在 P4_16 版本中,p4c 引用的编辑器包含 一个 eBPF 后端。
Brendan Gregg 为想要 使用 bcc 工具 跟踪和监视内核中的事件的人制作了一个非常好的 教程。第一个教程是关于如何使用 bcc 工具,它有许多章节,可以教你去理解怎么去使用已有的工具,而 针对 Python 开发者的一篇 专注于开发新工具,它总共有十七节 “课程”。
Sasha Goldshtein 也有一些 Linux 跟踪研究材料 涉及到使用几个 BPF 工具进行跟踪。
Jean-Tiare Le Bigot 的另一篇文章提供了一个详细的(和有指导意义的)使用 perf 和 eBPF 去设置一个低级的跟踪器 的示例。
对于网络相关的 eBPF 使用案例也有几个教程。有一些值得关注的文档,包括一篇 eBPF 卸载入门指南,是关于在 Open NFP 平台上用 Netronome 操作的。其它的那些,来自 Jesper 的演讲,XDP 能为其它人做什么(及其第二版),可能是 XDP 入门的最好的方法之一。
有示例是非常好的。看看它们是如何工作的。但是 BPF 程序示例是分散在几个项目中的,因此,我列出了我所知道的所有的示例。示例并不是总是使用相同的 helper(例如,tc 和 bcc 都有一套它们自己的 helper,使它可以很容易地去用 C 语言写 BPF 程序)
内核中包含了大多数类型的程序:过滤器绑定到套接字或者 tc 接口、事件跟踪/监视、甚至是 XDP。你可以在 linux/samples/bpf/ 目录中找到这些示例。
现在,更多的示例已经作为单元测试被添加到 linux/tools/testing/selftests/bpf 目录下,这里面包含对硬件卸载的测试或者对于 libbpf 的测试。
Jesper 的 Dangaard Brouer 在他的 prototype-kernel 仓库中也维护了一套专门的示例。 这些示例与那些内核中提供的示例非常类似,但是它们可以脱离内核架构(Makefile 和头文件)编译。
也不要忘记去看一下 git 相关的提交历史,它们介绍了一些特定的特性,也许包含了一些特性的详细示例。
iproute2 包也提供了几个示例。它们都很明显地偏向网络编程,因此,这个程序是附着到 tc 入站或者出站接口上。这些示例在 iproute2/examples/bpf/ 目录中。
许多示例都是 与 bcc 一起提供的:
tracing
目录包含许多 跟踪编程 的示例。前面的教程中提到的都在那里。那些程序涉及了很大部分的事件监视功能,并且,它们中的一些是面向生产系统的。注意,某些 Linux 发行版(至少是 Debian、Ubuntu、Fedora、Arch Linux)、这些程序已经被 打包了 并且可以很 “容易地” 通过比如 # apt install bcc-tools
进行安装。但是在写这篇文章的时候(除了 Arch Linux),首先要求安装 IO Visor 的包仓库。虽然 bcc 一般很容易在内核中去注入和运行一个 BPF 程序,将程序附着到 tc 接口也能通过 tc
工具自己完成。因此,如果你打算将 BPF 与 tc 一起使用,你可以在 tc-bpf(8)
手册页面 中找到一些调用示例。
有时候,BPF 文档或者示例并不够,而且你只想在你喜欢的文本编辑器(它当然应该是 Vim)中去显示代码并去阅读它。或者,你可能想深入到代码中去做一个补丁程序或者为机器增加一些新特性。因此,这里对有关的文件的几个建议,找到你想要的函数只取决于你自己!
syscall.c
中实现,而 core.c
包含了 解析器。其它文件的命名显而易见:verifier.c
包含 校验器(不是开玩笑的),arraymap.c
的代码用于与数组类型的 映射 交互,等等。act_bpf.c
(action)和 cls_bpf.c
(filter)中。一旦装载进内核的 BPF 虚拟机,由一个 Netlink 命令将 XDP 程序从用户空间钩入到内核网络路径中。接收它的是在 linux/net/core/dev.c 文件中的 dev_change_xdp_fd()
函数,它被调用并设置一个 XDP 钩子。钩子被放在支持的网卡的驱动程序中。例如,用于 Netronome 硬件钩子的 ntp 驱动程序实现放在 drivers/net/ethernet/netronome/nfp/ 中。文件 nfp_net_common.c
接受 Netlink 命令,并调用 nfp_net_xdp_setup()
,它会转而调用 nfp_net_xdp_setup_drv()
实例来安装该程序。
在 bcc 的 GitHub 仓库 能找到的 bcc 工具集的代码。其 Python 代码,包含在 BPF
类中,最初它在文件 bcc/src/python/bcc/__init__.py 中。但是许多我觉得有意思的东西,比如,加载 BPF 程序到内核中,出现在 libbcc 的 C 库中。
当然,这些代码与 iproute2 包中的 tc 中的 BPF 相关。其中的一些在 iproute2/tc/ 目录中。文件 f_bpf.c
和 m_bpf.c
(和 e_bpf.c
)各自用于处理 BPF 的过滤器和动作的(和 tc exec
命令,等等)。文件 q_clsact.c
定义了为 BPF 特别创建的 clsact
qdisc。但是,大多数的 BPF 用户空间逻辑 是在 iproute2/lib/bpf.c 库中实现的,因此,如果你想去使用 BPF 和 tc,这里可能是会将你搞混乱的地方(它是从文件 iproute2/tc/tc_bpf.c 中移动而来的,你也可以在旧版本的包中找到相同的代码)。
内核中也带有 BPF 相关的三个工具的源代码(bpf_asm.c
、 bpf_dbg.c
、 bpf_jit_disasm.c
),根据你的版本不同,在 linux/tools/net/ (直到 Linux 4.14)或者 linux/tools/bpf/ 目录下面:
bpf_asm
是一个极小的 cBPF 汇编程序。bpf_dbg
是一个很小的 cBPF 程序调试器。bpf_jit_disasm
对于两种 BPF 都是通用的,并且对于 JIT 调试来说非常有用。bpftool
是由 Jakub Kicinski 写的通用工具,它可以与 eBPF 程序交互并从用户空间的映射,例如,去展示、转储、pin 程序、或者去展示、创建、pin、更新、删除映射。阅读在源文件顶部的注释可以得到一个它们使用方法的概述。
与 eBPF 一起工作的其它必需的文件是来自内核树的两个用户空间库,它们可以用于管理 eBPF 程序或者映射来自外部的程序。这个函数可以通过 linux/tools/lib/bpf/ 目录中的头文件 bpf.h
和 libbpf.h
(更高层面封装)来访问。比如,工具 bpftool
主要依赖这些库。
如果你对关于 BPF 的不常见的语言的使用感兴趣,bcc 包含 一个 BPF 目标的 P4 编译器以及 一个 Lua 前端,它可以被用以代替 C 的一个子集,并且(用 Lua )替代 Python 工具。
这个 BPF 后端用于 clang / LLVM 将 C 编译到 eBPF ,是在 这个提交 中添加到 LLVM 源代码的(也可以在 这个 GitHub 镜像 上访问)。
到目前为止,我知道那里有至少两种 eBPF 用户空间实现。第一个是 uBPF,它是用 C 写的。它包含一个解析器、一个 x86_64 架构的 JIT 编译器、一个汇编器和一个反汇编器。
uBPF 的代码似乎被重用来产生了一个 通用实现,其声称支持 FreeBSD 内核、FreeBSD 用户空间、Linux 内核、Linux 用户空间和 Mac OSX 用户空间。它被 VALE 交换机的 BPF 扩展模块使用。
其它用户空间的实现是我做的:rbpf,基于 uBPF,但是用 Rust 写的。写了解析器和 JIT 编译器 (Linux 下两个都有,Mac OSX 和 Windows 下仅有解析器),以后可能会有更多。
正如前面所说的,如果你希望得到更多的关于一些特定的 BPF 特性的信息,不要犹豫,去看一些提交日志。你可以在许多地方搜索日志,比如,在 git.kernel.org、在 GitHub 上、或者如果你克隆过它还有你的本地仓库中。如果你不熟悉 git,你可以尝试像这些去做 git blame
去看看介绍特定代码行的提交内容,然后,git show
去看详细情况(或者在 git log
的结果中按关键字搜索,但是这样做通常比较单调乏味)也可以看在 bcc 仓库中的 按内核版本区分的 eBPF 特性列表,它链接到相关的提交上。
对 eBPF 的追捧是最近的事情,因此,到目前为止我还找不到许多关于怎么去排错的资源。所以这里只有几个,是我在使用 BPF 进行工作的时候,对自己遇到的问题进行的记录。
tc-bpf
(用于去编译 C 代码到 BPF 中)的 man 页面提供的 bcc
shell 函数时:我曾经必须添加包含 clang 调用的头文件:__bcc() {
clang -O2 -I "/usr/src/linux-headers-$(uname -r)/include/" \
-I "/usr/src/linux-headers-$(uname -r)/arch/x86/include/" \
-emit-llvm -c $1 -o - | \
llc -march=bpf -filetype=obj -o "`basename $1 .c`.o"
}
(现在似乎修复了)。
bcc
的其它问题,不要忘了去看一看这个工具集的 答疑。tc
去加载一个程序,确保你使用了一个与使用中的内核版本等价的 iproute2 中的 tc
二进制文件。bcc
去加载一个程序,确保在你的系统上安装了 bcc(仅下载源代码去运行 Python 脚本是不够的)。tc
,如果 BPF 程序不能返回一个预期值,检查调用它的方式:过滤器,或者动作,或者使用 “直传” 模式的过滤器。tc
,注意不使用过滤器,动作不会直接附着到 qdiscs 或者接口。tc
工具有一个 verbose
模式,它与 BPF 一起工作的很好:在你的命令行尾部尝试追加一个 verbose
。bcc
也有一个 verbose
选项:BPF
类有一个 debug
参数,它可以带 DEBUG_LLVM_IR
、DEBUG_BPF
和 DEBUG_PREPROCESSOR
三个标志中任何组合(详细情况在 源文件中)。 为调试该代码,它甚至嵌入了 一些条件去打印输出代码。-g
标志允许你通过内核校验器去以人类可读的格式去转储你的程序。处理转储文件,使用:$ llvm-objdump -S -no-show-raw-insn bpf_program.o
请经常会回到这篇博客中,来看一看 关于 BPF 有没有新的文章!
特别感谢 Daniel Borkmann 指引我找到了 更多的文档,因此我才完成了这个合集。
via: https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/
作者:Quentin Monnet 译者:qhwdw 校对:wxy
上一篇:容器环境中的代理模型