eBPF简介

eBPF简介

什么是eBPF?

eBPF(extended Berkeley Packet Filter)是一个非常强大的内核功能扩展,它允许开发人员在不修改内核代码的情况下运行特定的功能。本质为一个虚拟机。

起源于经典的 BPF(Berkeley Packet Filter),主要用于网络包过滤。eBPF在其基础上大幅扩展功能,演变为一个通用的内核级“虚拟机”,能够安全执行各种自定义程序,实现动态追踪、性能分析、安全策略等。

eBPF 的常见用途非常广泛,可以用于网络监控、安全过滤、性能分析和虚拟化等多种应用场景。

一、eBPF核心架构

ebpf虚拟机:内核中实现的一个安全沙箱,用于加载和执行 eBPF 程序,支持有限的指令集和寄存器模型

eBPF 程序:由用户空间编写,通常使用 C 语言,然后通过 LLVM /CLANG工具链编译成 eBPF 字节码。

内核验证器:负责验证 eBPF 程序的安全性 和正确性,确保不会执行非法操作、无限循环或者破坏内核稳定性。

JIT编译器:运行时即时把中间字节码编译成本地硬件机器码。

钩子点(hook points):eBPF 程序可以附加在各种内核事件上,如系统调用、网络栈、tracepoints、kprobes 等,实现动态插桩。

映射(maps):eBPF 程序与用户空间之间的数据结构,用于保存状态和共享数据,支持多种类型,如哈希表、数组、环形缓冲区等。

二、eBPF工作原理

这个经典原理图和后面的eBPF流程挂钩。

主要讲下其epf()系统调用的实现,及ebpf程序是怎么注入内核。

static int bpf_prog_load(union bpf_attr *attr) { enum bpf_prog_type type = attr->prog_type; struct bpf_prog *prog; int err; ... // 创建 bpf_prog 对象,用于保存 eBPF 字节码和相关信息 prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER); ... prog->len = attr->insn_cnt; // eBPF 字节码长度(也就是有多少条 eBPF 字节码) err = -EFAULT; // 把 eBPF 字节码从用户态复制到 bpf_prog 对象中 if (copy_from_user(prog->insns, u64_to_ptr(attr->insns), prog->len * sizeof(struct bpf_insn)) != 0) goto free_prog; ... // 这里主要找到特定模块的相关处理函数(如修正helper函数) err = find_prog_type(type, prog); // 检查 eBPF 字节码是否合法 err = bpf_check(&prog, attr); // 修正helper函数的偏移量 fixup_bpf_calls(prog); // 尝试将 eBPF 字节码编译成本地机器码(JIT模式) err = bpf_prog_select_runtime(prog); // 申请一个文件句柄用于与 bpf_prog 对象关联 err = bpf_prog_new_fd(prog); return err; ... }
  • bpf_prog_load()内核函数主要完成以下几个工作:
  1. 创建一个bpf_prog对象,用于保存 eBPF 字节码和 eBPF 程序的相关信息。
  2. 把 eBPF 字节码从用户态复制到bpf_prog对象的insns成员中,insns成员是一个类型为bpf_insn结构的数组。
  3. 根据 eBPF 程序所属的类型(如socketkprobesxdp等),找到其相关处理函数(如helper函数对应的修正函数,下面会介绍)。
  4. 检查 eBPF 字节码是否合法。由于 eBPF 程序运行在内核态,所以要保证其安全性,否则将会导致内核崩溃。
  5. 修正helper函数的偏移量(helper函数是 eBPF 提供给用户使用的一些辅助函数)
  6. 尝试将 eBPF 字节码编译成本地机器码,主要为了提高 eBPF 程序的执行效率。
  7. 申请一个文件句柄用于与bpf_prog对象关联,这个文件句柄将会返回给用户态,用户态可以通过这个文件句柄来读取内核中的 eBPF 程序。

三、eBPF流程

一般我们都是通过c语言编写BPF程序,然后通过clang/llvm编译器,将BPF程序编译为BPF字节码。然后通过bpf系统调用,将BPF字节码(伪机器码)注入到内核中,在注入的时候,我们必须要经过BPF程序的验证,来保证我们写的BPF程序没有问题,以防干掉我们的系统。然后,在判断是否开启了JIT,然后开启了,还需要将BPF字节码编译为本机机器码,以加快运行速度。还会把内核中的 BPF 程序绑定到内核钩子。

当我们BPF程序attach的事件触发了,就会执行我们的BPF程序,然如是经过JIT编译过后的就能够直接执行,然后没有开启JIT就需要通过虚拟机进行解析在执行。在执行BPF程序的过程中,会将需要保存的数据存储到map空间中,用户时候可以从map空间读取出数据。BPF程序的大致流程就是这个样子。

注意:BPF是基于事件触发的。这是什么意思呢?

就是如果我们将BPF程序attach到某个事件上,当这个事件触发的时候,就会执行我们这个BPF程序。其实这就是BPF的工作原理。

如,我们将BPF程序attach到kprobe类型的事件上,这个kprobe事件是个函数,当cpu执行到这个函数的时候,就会触发。然后就会执行我们的BPF程序。

参考

一文看懂eBPF|eBPF实现原理-CSDN博客

eBPF技术详解-CSDN博客