直播回放总结 | eBPF简介

语言: CN / TW / HK

什么是BPF

它是可以在内核中运行的沙盒程序,这个沙箱程序是可以被一些事件触发的,它的优点就是不需要修改内核的源代码,或者装载内核模块,是可以在用户态自定义它的功能,并且可以保证内核的安全性,属于一种新类型的软件,另外,虽然它是起源于内核,但是bpf在最近两年发展非常快,一个重要原因是它可以帮助我们用户态的应用提高性能,或者说做安全、可观测性等方面工作,并且它的能力非常强大,所以它非常适用于用户态场景,这也是bpf发展这么快的一个原因。

那么到底怎么形容bpf的功能,可以通过javascript做一个对比:我们有js之前,在网页上的是静态的网页,但是我们加入js之后,就可以在网页中实现一些更为复杂功能。比如说网页的实时刷新、在地图上做一个交互,在网页上点一个按钮控制影片播放/暂停等等。通过事件触发js。并且它是运行在沙盒里,具有安全性保证,性能也是由jit支持。而bpf和它这种非常类似,只不过bpf是在操作系统内核干了这件事。有了bpf之后就可以在内核运行时做一些实时性很强的事情,例如信息提取,另外也可在特定的场景下改变内核的一些行为。ebpf的事件不像js是一些硬件事件,而是内核函数,比如说在内核中某一个函数点被执行了,那么他有一个kprobe,或者traceprint这种内核里边的钩子,当内核在执行到这种函数时就会触发这个事件,所以说bpf是靠这些东西来触发的,由于他是在vm中运行,所以也能保证内核安全,另外,他的每一个程序码都需要进入到内核,所以对安全性要求非常高,verfier校验器就是来验证ebpf字节码是否安全,而对于性能,jit编译器则将bpf字节码编译成适合本机架构的机器码运行。

前面说的ebpf是内核事件触发,那么他到底是怎么一个事件触发的呢?其实我们关注的bpf事件就两个:实现的逻辑和数据的存储。数据存储是在bpf内部,因为这部分数据在内核中比较特殊,所以他有一个map结构,他是以k-v键值对的形式存储数据。事件触发对于probe来说,就是创建了一个bpf程序,并且已经load,但是程序不会在cpu上去执行,只有在某些内核事件触发的时候,CPU才会去执行bpf程序,当执行完成之后会被CPU调度走变成等待状态,等待事件被再次触发。经过这两年发展目前,bpf程序也支持锁(spin lock)和sleep状态,ebpf程序的实现逻辑简单来说就是代码,但是它使用的是一组接口稳定、功能受限的help函数。我们在内核中只能使用这些help函数来实现功能逻辑,不会像我们写用户代码或者内核模块那样随意调用函数。

ebpf 程序可由sockets、tracepoints、usat、kprobes、uprobes、perf_events事件驱动,这些都是目前内核里面基础事件,里面一部分是网络,一部分是tracing,tracing是做内核可观测性的,比如kprobe、uprobe,也就是说内核态或者是用户态函数我们都可以做挂载。通过int 3指令陷入到内核中,同时也可以做一些比如数据过滤之类的行为。Bpf正是利用了这些事件,来执行自己的一些代码逻辑,所以说这些事件的功能就很有可能是ebpf具有的功能。而这些功能存在的一些限制也很有可能是ebpf存在的限制。

Linux 系统不需要进行任何操作就已经在运行bpf程序,而要查看这些信息只要使用bpftool工具即可。它可以方便的对bpf程序进行管理、观测。

Bpftool prog show 命令即可查看当前系统运行的bpf程序。

BPF字节码

用户态的bpf程序经过clong、llvm编译后会形成bpf字节码,进入内核后有一个jit编译器将bpf字节码编译成本机架构能够识别的机器码,这样可以保证用户态的bpf程序不会因架构不同导致程序不能运行。

字节码组成:

查看bpf字节码

首先要向内核注入一个bpf程序:

该程序利用kprobe事件将程序挂载到do_sys_open内核函数上,获取进程pid和文件名,并使用bpf_printk函数将信息打印到trace_pipe文件中,通过访问文件内容即可查看打印信息。

运行结果:

Bpftool prog show 命令可以查看新注入的bpf程序:

使用Bpftool prog dump xlated id 40 命令查看新注入bpf程序详细信息

第一个红框中的函数会拿到当前进程pid,而这个函数就是bpf提供的接口稳定但功能受限的API。Bpf就是利用这些函数完成相关程序功能。

查看bpf字节码:

命令:Bpftool prog dump xlated id 40 opcode

内核中对相关字节码有对应定义,比如opcode内容由内核中#define BPF_CALL 0x80 和#define BPF_JMP 0x05 两个宏定义或运算得到,对应红框中第一个85字节码。80 10对应bpf_get_current_pid_tgid()函数字节码,在bpf_helper_defs.h文件中对该函数有如下定义:

经过检验器偏移计算即可得到对应字节码。

Bpf 进入内核入口:BPF Syscall

虽然bpf观测内核有别于内核模块,但是Bpf程序依旧逃不过利用系统调用进入内核,我们可以使用strace工具来追踪bpf程序调用的整个过程,内核对bpf系统调用定义如下:

从中可以看出代码中使用了大量的switch、case语句,而相关cmd在内核中定义如下:

其中BPF_MAP_CREATE表示bpf程序中有map结构,那么就会调用map_create()函数。

Verifier、jit

Verifier 、jit都发生在bpf_prog_load函数中,对应cmd=BPF_PROG_LOAD,这个函数主要功能如下:

1、 校验license

2、 判断bpf指令数目

3、 判断权限

4、 把bpf字节码从用户态拷贝到内核中

5、 Bpf_check() 进行verifier校验

6、 Bpf_prog_select_runtime() 进行jit编译

7、 分配id和文件句柄

Verifier

函数入口在bpf_check(),主要做如下事情:

1、 使用DAG的DFS算法对bpf程序的每一个字节码、每一条代码路径进行程序校验

2、 严格限制bpf程序对内存访问

3、 参数校验

4、 仅允许有限循环

5、 修改字节码,把函数编号替换成指针

JIT

入口:do_jit(),jit会将bpf字节码解析编译成本机能够允许的字节码,所以它和硬件的具体架构相关。若程序不支持JIT时,使用interpreter内核解析器解析程序。

一些bpf框架/脚手架/工具

现有的基于bpf开发的性能工具:

BCC ,bpftrace

基于bpf开发的复杂功能,产品:

Libbpf c (CO-RE),BCC python,cilium/ebpf Go、libbpf-rs

快速验证单个功能模型:

BCC

查看bpf信息,例如字节码、挂载点、prog、map等信息:

Bpftool(CLI tool) 、bpftrace

视频链接:

https://www.bilibili.com/video/BV1su411y71xspm_id_from=333.999.0.0