概览
程序
eBPF 最核心的部分是程序。eBPF 程序可以附加在内核的不同点,并且会像函数一样被调用。程序可以有许多不同的目的,例如,它们可以记录信息、修改信息、做出决定并引起副作用。程序可以附加到哪里以及允许它做什么取决于其程序类型。
程序是通过上下文调用的,该上下文是一个结构体,其中包含内核易于提供给程序的信息。典型的示例是套接字缓冲器或 CPU 寄存器。传递给程序的上下文取决于其类型。
类似程序的函数也有返回值,其含义再次由程序类型确定。例如,返回值可以指示要保留的数据包的字节量,也可以指示可以执行的操作的枚举,例如丢弃数据包、接受数据包或重定向数据包。
eBPF 程序通常是用 C 语言编写的,并使用 LLVM 编译,但这并不一定是唯一的方法。任何可以生成字节码(遵循 eBPF 指令集)的程序都可以编写 eBPF 程序。eBPF 程序通常被序列化为可重定位的 ELF 文件。
最终,eBPF 程序使用 BPF syscall 加载到内核中,执行此操作的用户空间程序被称为加载器。在实践中,加载器的范围从仅加载 eBPF 程序的应用程序到不断与多个程序和映射交互以提供高级功能的复杂系统。加载器通常使用加载器库来提供比 syscall 更高级别的 API,以简化开发。
当加载器加载程序时,内核将验证该程序是否“安全”。这项工作由称为验证器的内核组件完成。在这种情况下,“安全”意味着不允许程序使内核崩溃或破坏关键组件。eBPF 程序必须通过相当多的严格要求,然后才能被允许在内核内存附近的任何地方使用。有关更多详细信息,请查看验证者页面。
帮助程序函数
程序本身是相当有限的,它们可以从本地堆栈读取和写入,在寄存器上执行数学运算,调用内部函数并进行条件跳转。所有这一切都在它自己的小泡沫中。程序可以做的最后一件事是调用所谓的“辅助函数”。这些实际上是由内核定义的常规 C 函数。这些函数在 eBPF 程序和内核之间形成了一种内部 API/ABI。这些帮助程序可以允许 eBPF 程序执行它们原本无法执行的任务,因为它无法通过验证器。
这些帮助程序函数最多接受 5 个参数并返回单个返回值。并非每种程序类型都可以执行每个帮助程序调用,以强制执行与验证器相同的限制。
帮助程序函数具有多种用途,从简单地获取一些附加信息(例如我们正在执行的 CPU 内核)到调用主要的副作用(例如重定向数据包)。如需完整的概述,请查看帮助程序函数页面。
KFuncs
KFuncs 是已经过注解的内核函数,因此可以从 eBPF 程序中调用它们。它本质上是帮助程序函数的替代机制。上游内核原则上不再接受新的辅助函数,所以任何需要暴露给 eBPF 程序的新功能都应该通过 KFuncs 来完成。
KFuncs 不被视为 UAPI(用户空间 API),并且不受与 UAPI 相同的稳定性保证的约束。建议 KFuncs 的用户使用防御性编程技术来处理 KFunc 不可用或已更改的情况。
有关更多详细信息,请查看 KFuncs 页面。
映射(maps)
eBPF 映射是存在于内核中的数据结构。eBPF 程序和用户空间程序都可以访问这些映射,因此它们是 eBPF 程序和用户空间之间的通信层,也是在程序调用之间持久保存数据的地方。与所有其他 BPF 对象一样,映射在整个主机上共享,并且多个程序可以同时访问相同的映射。因此,映射也可用于在不同连接点的不同类型的程序之间传输信息。
这些映射的示例包括 BPF_MAP_TYPE_ARRAY
它是任意值的数组,或者 BPF_MAP_TYPE_HASH
是具有任意键和值类型的哈希映射。有关详细信息,请查看Map类型概述。
对象(objects)
eBPF 程序和映射是 BPF 对象,还有一些我们尚未提及的对象。所有这些对象都以大致相同的方式进行管理。此类 BPF 对象由加载器创建,加载器获取该对象的文件描述符。文件描述符用于与对象进一步交互,但它也是使对象保持“活动”的引用。一旦不存在对该对象的更多引用,对象就会被释放。
应用程序可以通过进程间通信技术(如 UNIX 套接字)将这些文件描述符的副本传输到其他进程,这是非常通用的。一种更 eBPF 的特殊技术称为 pinping
,它允许加载器使用称为 pin
的特殊文件引用 BPF 对象。这些 pin
只能在特殊的 BPF 文件系统中创建,该文件系统需要挂载到某个地方(通常位于 /sys/bpf
,但这在 Linux 发行版之间可能不同)。只要存在一个 pin
,它就会保持它所引用的对象在内核中保持活着。任何有权访问 pin
文件并以这种方式获取对象引用的程序都可以读取这些 pin
。因此,多个程序可以同时共享相同的对象。
Capabilities
从 Linux 5.8 开始,eBPF 功能变得更加细致。您可以在此处找到程序类型列表及其所需的功能。
CAP_BPF
:允许加载 eBPF 程序和创建 eBPF 映射。CAP_PERFMON
:加载跟踪程序时需要,对于bpf_trace_printk()
函数是必需的。CAP_NET_ADMIN
:加载网络程序时需要。
更多细节可以在内核头文件中找到。
关于程序类型 BPF_PROG_TYPE_CGROUP_SKB
的异常。它们可以由无权限用户加载,但不能附加 (attached)。