ebpf分析

总体架构

用户态

  1. 用户编写 eBPF 程序,可以使用 eBPF 汇编或者 eBPF 特有的 C 语言来编写。
  2. 使用 LLVM/CLang 编译器,将 eBPF 程序编译成 eBPF 字节码。
  3. 调用 bpf() 系统调用把 eBPF 字节码加载到内核。

内核态

  1. 当用户调用 bpf() 系统调用把 eBPF 字节码加载到内核时,内核先会对 eBPF 字节码进行安全验证。
  2. 使用 JIT(Just In Time)技术将 eBPF 字节编译成本地机器码(Native Code)。
  3. 然后根据 eBPF 程序的功能,将 eBPF 机器码挂载到内核的不同运行路径上(如用于跟踪内核运行状态的 eBPF 程序将会挂载在 kprobes 的运行路径上)。当内核运行到这些路径时,就会触发执行相应路径上的 eBPF 机器码。

Bytecode

eBPF 程序最终会被编译成 eBPF 字节码,eBPF 字节码使用 bpf_insn 结构来表示,如下:

struct bpf_insn {

    __u8    code;       // 操作码

    __u8    dst_reg:4;  // 目标寄存器

    __u8    src_reg:4;  // 源寄存器

    __s16   off;        // 偏移量

    __s32   imm;        // 立即操作数

};

  1. code:指令操作码,如 mov、add 等。
  2. dst_reg:目标寄存器,用于指定要操作哪个寄存器。
  3. src_reg:源寄存器,用于指定数据来源于哪个寄存器。
  4. off:偏移量,用于指定某个结构体的成员。
  5. imm:立即操作数,当数据是一个常数时,直接在这里指定。

  • ebpf程序会被LLVM/clang编译成bpf_insn数组,当内核要执行 eBPF 字节码时,会调用 __bpf_prog_run() 函数来执行。
  • 如果开启了JIT(Just-In-Time),内核会将 eBPF 字节码编译成本地机器码(Native Code)。这样就可以直接执行,而不需要虚拟机来执行。

ebpf虚拟机

  • eBPF 虚拟机的作用就是执行 eBPF 字节码,eBPF 虚拟机比较简单(只有300行代码左右),由 __bpf_prog_run() 函数实现。

  • eBPF 虚拟机的运行环境只有 1 个 512KB 的栈和 11 个寄存器(还有一个 PC 寄存器,用于指向当前正在执行的 eBPF 字节码)。如下图所示

ebpf加载器

用户态可以通过调用 sys_bpf() 系统调用把 eBPF 程序加载到内核,而 sys_bpf() 系统调用会通过调用 bpf_prog_load() 内核函数加载 eBPF 程序

用户态 -> 系统调用 -> sys_bpf -> bpf_prog_load

bpf_prog_load() 函数主要完成以下几个工作:

 1. 创建一个 bpf_prog 对象,用于保存 eBPF 字节码和 eBPF 程序的相关信息。

  1. 把 eBPF 字节码从用户态复制到 bpf_prog 对象的 insns 成员中,insns 成员是一个类型为 bpf_insn 结构的数组。
  2. 根据 eBPF 程序所属的类型(如 socket、kprobes 或 xdp 等),找到其相关处理函数(如 helper 函数对应的修正函数,下面会介绍)。
  3. 检查 eBPF 字节码是否合法。由于 eBPF 程序运行在内核态,所以要保证其安全性,否则将会导致内核崩溃。
  4. 修正 helper 函数的偏移量(下面会介绍)。
  5. 尝试将 eBPF 字节码编译成本地机器码,主要为了提高 eBPF 程序的执行效率。
  6. 申请一个文件句柄用于与 bpf_prog 对象关联,这个文件句柄将会返回给用户态,用户态可以通过这个文件句柄来读取内核中的 eBPF 程序。

修正 helper 函数 (ebpf程序如何调用一些内核函数?)

helper 函数是 eBPF 提供给用户使用的一些辅助函数。

由于 eBPF 程序运行在内核态,所为了安全,eBPF 程序中不能随意调用内核函数,只能调用 eBPF 提供的辅助函数(helper functions)。

注意,这里的helper函数和架构图中的kernel function不是同一个。

每个 eBPF 的 helper 函数都有一个编号(通过枚举类型 bpf_func_id 来定义),定义在 include/uapi/linux/bpf.h 文件中,定义如下(只列出一部分):

在 eBPF 程序中怎么调用 helper 函数:

上述指定的函数(BPF_FUNC_trace_printk)需要重定向要内核函数。

加载器会通过调用 fixup_bpf_calls() 函数来修正 helper 函数的地址。我们来看看 fixup_bpf_calls() 函数的实现(通过修改bytecode中 call/jump #addr为 #真实的内核函数地址):

开发简单示例

使用 C 编写 eBPF 内核程序

  • 新建一个 hello.c 文件,并输入下面的内容:

int hello_world(void *ctx)

{

    bpf_trace_printk("Hello, World!");

    return 0;

}

使用 Python 和 BCC 工具开发一个用户态程序

  • 新建一个 hello.py 文件,并输入下面的内容:

#!/usr/bin/env python3

# 1) 加载 BCC 库

from bcc import BPF

# 2) 加载 eBPF 内核态程序

b = BPF(src_file="hello.c")

# 3) 将 eBPF 程序挂载到 kprobe

b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")

# 4) 读取并且打印 eBPF 内核态程序输出的数据

b.trace_print()

  1. 导入了 BCC 库的 BPF 模块,以便接下来调用
  2. 调用 BPF() 函数加载 eBPF 内核态程序(也就是我们编写的hello.c,会自动编译成bytecode)。
  3. 将ebpf程序挂载到内核探针(kprobe),do_sys_openat2是要hook的内核函数,相应的handler为hello_world函数。
  4. 读取内核调试文件 /sys/kernel/debug/tracing/trace_pipe 的内容(bpf_trace_printk() 函数会将信息写入到此文件),并打印到标准输出中。

更丰富的开发程序见[3]。

参考资料

[1] 一文看懂eBPF、eBPF的使用(超详细) - 知乎

[2] 一文看懂eBPF|eBPF实现原理(超详细) - 知乎

[3] eBPF入门开发实践教程十七:记录 TCP 连接信息--tcpstates与tcprtt

### 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、付费专栏及课程。

余额充值