没有合适的资源?快使用搜索试试~ 我知道了~
eBPF - 程序员视角下的介绍 摘要: eBPF允许软件开发人员编写在内核中执行的程序,而不需要重新编译和系统重启。当调用内核函数时,这些程序可以收集关键性能指标。在本文中,我们将使用libbpf描述和讨论eBPF的体系结构以及其核心组件。我们将研究eBPF程序与典型用户空间C程序之间的关键区别。最后,我们将探讨eBPF的一些真实世界用例。然而,本文不会讨论性能数据或正式证明。本文仅是对阅读eBPF教材、博客文章、eBPF样本和内核代码的无数小时的总结。
资源推荐
资源详情
资源评论
eBPF - 程序员视角下的介绍
摘要:
eBPF 允许软件开发人员编写在内核中执行的程序,而不需要重新编译和系统重启。当
调用内核函数时,这些程序可以收集关键性能指标。在本文中,我们将使用 libbpf 描述和讨
论 eBPF 的体系结构以及其核心组件。我们将研究 eBPF 程序与典型用户空间 C 程序之间的
关键区别。最后,我们将探讨 eBPF 的一些真实世界用例。然而,本文不会讨论性能数据或
正式证明。本文仅是对阅读 eBPF 教材、博客文章、eBPF 样本和内核代码的无数小时的总
结。
1 致谢
我要向 Quentin Monnet 表示感谢,他在验证论文正确性以及提供有价值的反馈方面做
出了巨大贡献。
2 简介
BPF(Berkeley Packet Filter)于 1992 年出现,作为一种高效的网络数据包过滤器 [4, 11]。
网络数据包过滤器是一种网络安全机制,通过检查数据包在通过过滤器时的流动来控制网络
的流入和流出。BPF 的作者描述它比现有技术快 20 倍。BPF 与先前的系统不同之处在于,
在基于寄存器的 CPU 上构建的虚拟机中运行程序,并具有不需要复制所有信息以做出决策
的每个应用程序缓冲区 [4]。BPF 成为了当时最先进的技术,并被采用为网络数据包过滤的
首选技术。
Alexei Starovoitov 于 2014 年将 eBPF 作为 BPF 的现代硬件重新设计 [15, 4]。eBPF 虚拟
机更快,因为它更类似于现代处理器,因此允许 eBPF 指令与硬件指令集体系结构(ISA)紧
密映射[7]。在 2014 年 Alexei 的提交中 [15],eBPF 暴露给用户空间,随后,eBPF 不再限于
网络堆栈,并随着时间的推移变得更加广泛和通用。eBPF 使得在不需要重新编译内核和重
启系统的情况下更新内核行为成为可能,并提供了比模块编程更简单、更安全的接口。
eBPF 采用静态验证器构建,确保程序不能导致内核崩溃,并且始终能够终止。在程序编译
后,eBPF 验证器检查程序是否安全运行 [4,5]。
在内核运行 eBPF 程序之前,必须确定它的执行点。执行点由 eBPF 程序类型定义,在本文
后面将进行描述。eBPF 架构还包括映射,这是双向数据结构,允许 eBPF 程序与用户空间异
步共享数据 [4]。
在本文中,我们主要从 libbpf 的角度来看 eBPF。虽然存在其他方法,但使用 C 程序的
推荐方法是 libbpf。我们首先将介绍 eBPF 和 libbpf 的高级架构,然后转向一些实际示例和
实际用例。
3 架构
list1 是一个 eBPF 程序的示例,它附加到 kill 系统调用。它可用于安全和审
计目的,通过记录和记录未能正常终止的进程来记录。由于 eBPF 程序在内核中
运行,因此没有办法在没有升级系统特权的情况下防止此日志记录。
SEC 宏用于告诉编译器将字节码放置在指定的 ELF 节中。该节名称稍后会被加
载程序捡起,然后推断出附加类型。部分名称不是 eBPF 惯例,而是加载 eBPF
程序的程序的惯例。除了保留的名称之外,在 libbpf 中,部分名称应该使用
“_
section
(name)”约定。
# include < linux / bpf .h >
# include < bpf / bpf_helpers .h >
struct syscalls_enter_kill_args {
long long pad ;
long syscall_nr ;
long pid ;
long sig ;
};
SEC (" tracepoint / syscalls / sys_enter_kill ")
int kill_example ( struct
syscalls_enter_kill_args * ctx ) {
if( ctx - > sig != 9) return 0;
char fmt [] = " PID %u is being killed !\n";
bpf_trace_printk ( fmt , sizeof ( fmt ) , ctx - >
pid , sizeof ( ctx - > pid ) ) ;
return 0;
}
char _license [] SEC (" license ") = " GPL ";
list1
一个用 C 语言编写的 kill eBPF 示例。这个示例是一个 tracepoint eBPF 程序(参见 4.2.1 节)。
关键词比如 maps,探针首先由类型定义,然后是 hook。例如,在清单 1 中,程序类型
是 tracepoint,hook 是 syscalls/sys_enter_kill。
SEC 宏在 libbpf 的 bpf/bpf_helpers.h 文件中定义。在编译文件时,SEC 宏被一个
__attribute__语句替换,这是 GNU C 中一种向函数声明附加特性的机制[8]。
我们使用 bpf_trace_printk,在内核中将跟踪信息打印到通用跟踪管道 1 中。该函数提
供类似于 printf 的功能,但在内核空间中。我们将在第 4 节中更详细地描述探测器类型。
所有的 eBPF 程序都以上下文作为参数。对于跟踪程序,上下文包含有关内核当前正在
处理的信息,包括寄存器或函数参数[4]。上下文取决于 eBPF 程序的类型以及探针的位置。
在前面的示例中,上下文是 syscalls_enter_kill_args 结构体,该结构体遵循内核发布的格式
[2]。前 8 个字节未使用,应该被忽略。我们将在第 4 节中更详细地描述上下文参数。
在最后声明了 eBPF 程序的许可证。由于内核是根据 GPL 许可证授权的,因此某些 eBPF
程序需要与 GPL 兼容。其他程序(如网络程序)则不必与 GPL 兼容,可以采用专有许可证。
是否必须使 eBPF 程序与 GPL 兼容取决于多种因素,包括程序类型和使用的 helper 函数。
3.1 范围 eBPF 程序不能调用任意内核函数[5]。这是一种设计选择,因为它会
将 eBPF 程序绑定到特定的内核版本,从而增加兼容性的复杂性。但是,eBPF
程序可以调用内核提供的一组 helper 函数。 eBPF helper 函数的示例包括:
• 随机数生成。
• 访问当前时间。
• 访问 eBPF 映射。
• 获取进程/控制组上下文。
• 修改网络数据包。
原则上,如果满足验证器的要求并且编译器能够将它们内联到代码中,eBPF 程
序可以调用外部库函数。
3.2 编译 本文的其余部分假定您已经有了一个可运行示例的 eBPF 环境。如
果您之前没有在您的系统上编译或运行过 eBPF 程序,请阅读附录 A。
eBPF 是一种低级语言,即一种指令集架构,可以从高级语言(如 Rust 和 C)中
进行编译[4, 6]。本文将重点介绍 C 和使用 clang 和 llc 进行编译。选择
clang 而不是 GCC 的根源在于两个编译器中 eBPF 的成熟度。clang 已经有了与
eBPF 更长的历史,因此在 eBPF 社区中被视为参考工具。
在使用 clang/LLVM 工具链时,可以分为一步或两步编译。首先编译为
LLVM 中间表示(LLVM IR),然后在第二步使用 llc 可以更细致地控制传递给
llc 的选项。通过两步编译列表 1,可以先调用以下命令来完成第一步。
$ clang -target bpf -S -D __BPF_TRACING__
-I./libbpf/src/root/usr/include/ -Wall
-Werror -O2 -emit-llvm -c -g kill.c
我们选择使用优化级别 2 进行编译,因为这是大多数 eBPF 程序所必需的级别。如果没
有它,可能会生成具有重堆栈的次优代码,并且可能会引用一些被调用的函数。我们使用-
S、-c 和-emit-llvm 参数进行编译,以发出 LLVM IR 文件而不是典型的目标文件。我们将目
标架构设置为 BPF,以避免使用本地系统架构进行编译。这样做可能会产生无效代码或包含
无效的 ELF 节。我们还使用-D 参数进行编译,该参数启用了一些 eBPF 所需的功能,例如
ASM_GOTO 支持。-I 包括 libbpf 库,-Wall 和-Werror 参数将停止编译,如果 eBPF 程序有
剩余17页未读,继续阅读
资源评论
York·Zhang
- 粉丝: 1353
- 资源: 16
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功