跳转至

eBPF简介

BFP

BPF 全名为 BSD Packet Filter,最初被应用于网络监测,例如知名的TCPdump 工具中,它可以在内核态根据用户定义的规则直接过滤收到的包,相较竞争者 CSPF 更加高效

它设计了一个基于寄存器的虚拟机用来过滤包,而 CSPF 则使用的是基于栈的虚拟机

BPF 有两个组成部分:

  • Tap 部分负责收集数据
  • Filter 部分负责按规则过滤包

img

收到包以后,驱动不仅会直接发给协议栈,还会发给 BPF 一份, BPF根据不同的filter直接“就地”进行过滤,不会再拷贝到内核中的其他 buffer 之后再就行处理,否则就太浪费资源了。处理后才会拷贝需要的部分到用户可以拿到的 buffer 中,用户态的应用只会看到他们需要的数据

注意在 BPF 中进行处理的时候,不是一个一个包进行处理的,因为接收到包之间的时间间隔太短,使用 read 系统调用又是很费事的,所以 BPF 都是把接收到的数据打包起来进行分析,为了区分开这些数据,BPF 会包一层首部(header),用来作为数据的边界

经典的 BPF 的工作模式是用户使用 BPF 虚拟机的指令集定义过滤表达式,传递给内核,由解释器运行,使得包过滤器可以直接在内核态工作,避免向用户态复制数据,从而提升性能,比如 tcpdump 的 BPF 过滤指令实例如下:

> tcpdump -d port 80
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 10
(002) ldb      [20]
(003) jeq      #0x84            jt 6    jf 4
(004) jeq      #0x6             jt 6    jf 5
(005) jeq      #0x11            jt 6    jf 23
(006) ldh      [54]
(007) jeq      #0x50            jt 22   jf 8
(008) ldh      [56]
(009) jeq      #0x50            jt 22   jf 23
(010) jeq      #0x800           jt 11   jf 23
(011) ldb      [23]
(012) jeq      #0x84            jt 15   jf 13
(013) jeq      #0x6             jt 15   jf 14
(014) jeq      #0x11            jt 15   jf 23
(015) ldh      [20]
(016) jset     #0x1fff          jt 23   jf 17
(017) ldxb     4*([14]&0xf)
(018) ldh      [x + 14]
(019) jeq      #0x50            jt 22   jf 20
(020) ldh      [x + 16]
(021) jeq      #0x50            jt 22   jf 23
(022) ret      #262144
(023) ret      #0

执行过程如下:

img

Linux 3.0 中增加了JIT(即时编译),性能比解释执行更快,类似 java 的虚拟机,可以解释执行也可以即时编译执行

现在 BPF 的执行过程如下示意图:

img

(1)编写 eBPF 代码

(2)将 eBPF 代码通过 LLVM 把编写的 eBPF 代码转成字节码

(3)通过 bpf 系统调用提交给系统内核

(4)内核通过验证器对代码做安全性验证(包括对无界循环的检查)

(5)只有校验通过的字节码才会提交到 JIT 进行编译成可以直接执行的机器指令

(6)当事件发生时候,调用这些指令执行,将结果保存到 map 中

(7)用户程序通过映射来获取执行结果

eBPF

eBPF (extended BPF)

对于算法和数据结构应该大家都不陌生,在这门学科的语境里用 O(xxx)来衡量算法的复杂度。但是实际的工作中性能工程师要回答的常常不是时间复杂度问题,而是

  • 程序的哪个部分慢?
  • 慢的部分,单次执行的耗时是多少?

eBPF 能在不改一行代码的情况下,知道给定函数单次执行的耗时,并且是纳秒级别的精度。

官方:

Linux 内核一直是实现监控/可观测性、网络和安全功能的理想环境。 不过很多情况下这并非易事,因为这些工作需要修改内核源码或加载内核模块, 最终实现形式是在已有的层层抽象之上叠加新的抽象。 eBPF 是一项革命性技术,它能在内核中运行沙箱程序(sandbox programs), 而无需修改内核源码或者加载内核模块。

将 Linux 内核变成可编程之后,就能基于现有的(而非增加新的)抽象层来打造更加智能、 功能更加丰富的基础设施软件,而不会增加系统的复杂度,也不会牺牲执行效率和安全性。

原理

进程运行在操作系统(kernel)之上,也就是说进程干了什么,现在在干什么操作系统是清清楚楚的。

这样的话!问题 2 的解决方案就变成了告诉操作系统,让它帮忙盯一下给定函数在什么时候开始执行,并且什么时候执行完成。我们只要拿到这两个时间点就能算出特定函数单次执行的耗时。

image-20230221224440501

那我们怎么才能告诉操作系统呢?

Linux 内核里面有一个叫 eBPF 的框架,它是我们与内核对话的接口人。我们使用一门叫 bpftrace 的语言就可以和它对话,把我们想要做的事情告诉它。

eBPF 的整体的架构如下:

img