eBPF介绍

eBPF(扩展Berkeley包过滤器)起源于1992年的网络数据包过滤框架,旨在减少无效的数据包拷贝。随着发展,eBPF功能扩展,支持多种事件类型,如XDP、Perf Event、kprobe等,同时引入Map机制,支持用户空间与内核空间通信。eBPF的使用包括网络监控、性能调优等,常用工具如bcc和bpftrace。本文详细介绍了eBPF的历史、工作原理、使用方法,以及适用于初学者和高级用户的教程资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.BPF起源

BPF源头起源于一篇1992年的论文,这篇论文主要提出一种新的网络数据包的过滤的框架,如下图所示。

image.png

提出bpf的原因其实也很简单,早期我们从网卡中接收到很多的数据包,我们要想从中过滤出我们想要的数据包,我们需要将网卡接收的数据包都要从内核空间拷贝一份到用户空间。然后,用户程序在对这些进行过滤。那么,我们可以从中就能够发现一个问题。数据包必须全部拷贝。然后再过滤出所需的数据包,那么对于那些不需要的数据包,我们拷贝的操作是无效的、浪费的。并且对于内存数据的拷贝是很费cpu系统的资源的。所以,这篇论文,就提出了一种新的框架,在内核中直接过滤,这也可以避免一些无用的、浪费的拷贝。

其背后的思想其实就是:与其把数据包复制到用户空间执行用户态程序过滤,不如把过滤程序灌进内核去。

这种新的框架,其实还是很容易理解的。大概的理解就是,当我们从网卡接收到一个数据包的时候,我们数据链路层,将数据包额外的拷贝一份。然后这个新的数据包就交给BPF程序进行处理,这个BPF根据用户编写的过滤规则对这个新的数据包进行匹配。如果符合此规则就将数据包放到接收队列中,那么用户事后就可以从接收队列中将这个数据包从内核空间拷贝到用户空间,这样就减少了无用的数据包的拷贝。

像tcpdump/wireshark等用户工具就是基于BPF框架实现的。其大概实现的过程就是,编写BPF指令集的过滤规则,然后创建raw/packet类型的套接字socket,将网卡设置为混杂模式。在通过setsockopt函数将BPF代码拷贝到内核,并attach到相关联的socket套接字上。当网卡接收到数据包的时候,因为设置的混杂模式,那么就会额外的拷贝一份新的数据包,然后在根据BPF的代码进行过滤,将符合规则的数据包接收到socket套接字的接收队列里面。最后用户程序就可以从这个接收队列获取到过滤后的数据包了。这类工具的实现流程就是大概这个样子。

2.伪机器码、BPF指令集、JIT

使用过tcpdump工具的应该都见过在tcpdump命令后面会加一些表达式,用来表示过滤规则。

如:sudo tcpdump -d -i lo tcp and dst port 7070

注意不要以为这个表达式就是BPF程序了,其实这不是的。这个表达式是要经过编译过后才会变成BPF程序的。在我们早期是生产这类编译器,那么是如何将这个表达式编译出BPF指令集的呢?

tcpdump的实现是基于libcap库的,tcpdump使用的过滤表达式是使用libcap库进行解析的,生成我们BPF指令集。那为什么没有单独做成一个这类的编译器?究其原因就是但是的BPF框架使用的功能较少,只用在了网络的数据包过滤方面。除此之外,当时的BPF指令集个数很少,所以没有必要花费大量的资源单独做一个编译器。但是随着BPF的发展,指令集的复杂、支持的BPF程序类型越来越多,就急需要一个编译器了。那这个就是我们后面将要提到的eBPF和clang/llvm编译器了。

伪机器码:假的机器码,机器码都是能够在物理机上直接执行的,伪机器码不能够直接执行,需要在虚拟机上执行。

BPF指令集:BPF指令集就是一个伪机器码,是不能够在物理机上直接执行的,需要一个虚拟机才能够执行。我们都知道不同的处理器体系结构有自己的不同指令集,这边的BPF指令集可以理解为在BPF虚拟机上执行的指令集。

JIT:just in time 的缩写,我们将编译好的BPF指令集需要在虚拟机上执行,虚拟机需要一条一条的解析为本机机器码才能够执行,所以这个执行效率会很低,但是如果我们的处理器有了JIT就能够将我们BPF直接直接编译为能够在机器直接执行的机器码,这样大大提高了执行的速度。

3.eBPF介绍

eBPF是extend BPF的简称,扩展的BPF。我们刚了解BPF了,都知道BPF的功能比较单一只能够作用于网路的数据包的过滤上,但是扩展后的BPF的功能得到了很大的丰富,可以这样说基本上可以使用在Linux各个子系统中。除了功能上的扩展,BPF程序的指令集也变得相当复杂了,所以就出现了专门用于编译BPF程序的clang/llvm编译。在框架上BPF的框架也发生了变化,所以扩展后的BPF不再是早期的BPF的可以比拟的。因而,早期的BPF被称为cBPF,扩展后的BPF被称为eBPF。

现在看下扩展后的BPF的框架,如下图所示:

image.png

注意:我们后面说的BPF指的是cBPF和eBPF的统称,除非特别说明。

虽然,框架发生变化,但是其基本的思想还没有发生变化的。都是将BPF程序进行编译后生成字节码,然后将BPF字节码注入到内核中,当发生事件触发的时候,我们就会执行相应的BPF程序。

现在,我们对cBPF和eBPF进行对比:

一、cBPF支持的功能比较单一,只能够作用于网络的数据包的过滤上。而eBPF除了能够支持网络的数据包的过滤上,也支持其他的事件类型,如上图中的XDP、Perf Event、kprobe、tracepoint等等。cBPF的功能在eBPF其实对应的就是Socket的部分。

二、引入Map机制。在cBPF我们通过接收队列将过滤后数据获取出来,但是在eBPF我们可以将数据放到Map空间中。Map空间是用户空间和内核空间共享的,所以一般是在内核中将数据存入到Map空间中,然后在用户空间取出数据。

三、指令集变得更复杂了,与此同时,有了专门的用于编译BPF字节码的编译器clang/llvm。

四、还有在安全机制方面等等一些改变。

4.eBPF类型和Map机制

首先,看下eBPF支持的类型,其中BPF_PRO

### Linux eBPF 使用教程与案例 #### 什么是eBPFeBPF(Extended Berkeley Packet Filter)是一项允许在Linux内核中运行沙盒程序的技术,这些程序可以在特定事件发生时执行而不会影响系统的稳定性或安全性[^2]。 #### 安装bcc工具集 对于希望快速上手并利用现成工具来探索eBPF功能的人来说,安装`bcc`是一个不错的选择。这是一个基于Linux eBPF的新一代网络分析工具集合,适用于性能监控、网络跟踪以及安全分析等多个领域。可以通过访问项目主页获取更多详情和安装指南[^1]: ```bash pip install bcc ``` #### Go语言开发eBPF入门 如果倾向于使用Go语言进行eBPF应用的构建,则可以从官方文档开始学习如何设置环境,并编写简单的eBPF程序。这不仅限于理论介绍,还包括实际操作指导,帮助开发者掌握必要的技能: - **第一步**: 获取所需的依赖项和其他资源。 - **第二步**: 编写用户空间应用程序接口(API),用于配置和管理内核中的eBPF代码。 #### 实践案例:HTTP流量控制 为了更直观地理解eBPF的应用场景,考虑一个具体的例子——基于TC(Traffic Control)机制实现HTTP请求过滤的功能。此过程中涉及到内核态C代码编写及用户态Go代码交互的设计思路[^3]。 ##### 用户态Go文件示例: ```go package main import ( "fmt" ebpf "github.com/cilium/ebpf" ) func main() { // 加载已编译好的eBPF对象文件 obj, err := ebpf.LoadCollectionSpec("http_filter.bpf.o") if err != nil { fmt.Printf("Failed to load BPF object: %v\n", err) return } } ``` #### 构建支持eBPF的静态库 当需要将多个eBPF组件集成到更大的软件系统中时,创建一个包含常用函数和支持结构的静态库(`libbpf.a`)会很有帮助。下面展示了怎样从源码树下提取必要部分并打包为静态库[^4]: ```bash cd /path/to/kernel/source/tools/lib/bpf/ sudo ar rcs libbpf.a *.o ``` #### 性能优化技巧 针对某些具体应用场景下的性能瓶颈问题,比如频繁读写的I/O密集型任务,可以借助eBPF捕获感兴趣的文件描述符活动,并进一步挖掘潜在的调优机会。例如,通过关联socket信息来追踪TCP连接状态变化等[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值