问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

eBPF的工作原理:从历史演变到内部机制详解

创作时间:
作者:
@小白创作中心

eBPF的工作原理:从历史演变到内部机制详解

引用
1
来源
1.
https://www.bluepuni.com/archives/ebpf-internals/

eBPF(扩展可编程伯克利数据包过滤器)是一种在Linux内核中安全高效执行用户定义程序的通用环境。它最初是为加速TCP dump和包过滤而设计的,现已演变为一种强大的内核扩展机制,广泛应用于网络、安全、性能监控等领域。本文将深入探讨eBPF的工作原理,包括其历史演变、内部机制、编译过程、事件附着方式以及数据交互等核心概念。

eBPF的历史

BPF(Berkeley Packet Filter)最早出现在BSD系统中,主要用于加速TCP dump和包过滤。它通过让用户空间定义过滤器并将其编译成最高效的指令,在包接收流程的最短路径上运行,从而实现高性能的包过滤。

2013年,BPF得到了重大改进,演变为eBPF(扩展BPF)。eBPF在规格上进行了扩展,使用64位字长,增加了更多的寄存器,并提供了几乎无限的存储空间(通过map结构体)。更重要的是,eBPF不再局限于包过滤,而是可以附着在各种不同的事件源上,在Linux内核内部运行用户定义的程序。

eBPF的内部机制

上图展示了一个追踪工具的简化工作流程。在用户空间,BPF工具包含一个BPF程序,该程序被编译为BPF字节码。字节码随后被传输到内核部分的验证器进行验证。验证通过后,程序可以附着到不同的事件源。事件源在用户空间就已经确定,并且在执行过程中,可以通过perf buffer或map进行内核态到用户空间的数据传输。

BPF程序的转换

下面将详细解释BPF程序从源代码到字节码的转换过程。

AST(抽象语法树)

BPF程序首先被解析为抽象语法树(AST)。这个过程使用lex和yacc工具完成。例如,当程序中使用内置变量pid时,lex会将其识别为builtin,然后在yacc中匹配并分配相应的AST节点。类似地,printf、字符串以及kprobe等元素也会被解析为AST节点。

Clang解析器

接下来,使用Clang解析器处理结构体信息。这一步主要在内核中发挥作用,当需要遍历结构体或获取追踪点参数信息时,Clang解析器会解引用并获取正确的内存偏移量。然而,在本例中,由于没有使用结构体,因此可以跳过这一步。

语义分析

语义分析阶段检查语法错误,并调用内部函数创建map和probe。在本例中,分析器检测到了错误的pidd输入。

LLVM IR

通过语义分析后,程序被视为合法(尽管尚未通过验证)。接下来,使用LLVM编译器生成BPF字节码。LLVM支持BPF目标,因此可以先将程序预处理为LLVM IR,然后再生成字节码。

BPF字节码

最终生成的BPF字节码遵循Linux内核中定义的格式。eBPF使用64位指令宽度。例如,上面的示意图展示了call get_current_pid_tgid指令的字节码表示。

内核虚拟机

BPF字节码生成后,需要传递给内核进行进一步处理。

bpf()系统调用

使用strace工具可以追踪BPF字节码的加载过程,这通常涉及一个bpf()系统调用。

验证器

字节码传递给内核后,首先由验证器确认其安全性。验证器检查指令的合法性,包括内存读写范围是否合法、代码路径是否有限(可达)等。例如,验证器会检查函数ID的范围,确保没有无限循环或回跳。

JIT编译

通过验证后,字节码可以被JIT(即时编译)编译为机器码。JIT编译不仅提高了执行速度,还增强了安全性。新版内核已经不再使用解释器,而是推荐使用JIT编译。

事件附着

接下来介绍BPF程序如何附着到特定事件上。

kprobe实现

kprobe利用ftrace机制,在函数调用时触发探测点。ftrace在内核启动时将__fentry__调用替换为nop指令,在需要时再替换回fentry。BPF/kprobe只需将处理例程添加到fentry上即可。

tracepoint实现

tracepoint是一种静态追踪手段,预先定义在内核源代码中。与kprobe相比,tracepoint更稳定,适合长期使用的工具。tracepoint通过在编译时预留nop指令,在使能时替换为jmp指令,停用时再恢复为nop指令。

数据交互

最后介绍数据的汇总和输出机制。

输出缓冲区

printf()使用perf buffer进行数据输出。perf buffer为每个CPU核心提供一个缓冲区,用户空间通过epoll_wait()收集数据。由于数据可能乱序,需要在应用层进行排序处理。

map

map是一种键值对存储结构,用于在内核和用户空间之间传递数据。常见的类型包括hash map,可以使用bpf()系统调用创建,并通过BPF辅助函数进行操作。需要注意的是,遍历map会产生大量系统调用,因此通常在程序退出时一次性输出数据。

通过以上讨论的一系列流程,我们得到了最终的eBPF程序执行结果。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号