抽丝剥茧:从 Linux 源码探索 eBPF 的实现

抽丝剥茧:从 Linux 源码探索 eBPF 的实现

去年学习 eBPF,分享过 几篇 eBPF 方面的学习笔记,都是面向 eBPF 的应用。为了准备下一篇文章,这次决定从 Linux 源码入手,深入了解 eBPF 的工作原理。因此这篇又是一篇学习笔记,假如你对 eBPF 的工作原理也感兴趣,不如跟随我的脚步一起。文章中若有任何问题,请不吝赐教。

这里不会再对 eBPF 进行过多的介绍,可以参考我的另一篇 使用 eBPF 技术实现更快的网络数据包传输,结合 追踪 Kubernetes 中的数据包 可以了解 eBPF 的基本内容以及其在网络加速方面的应用。

接下来我们还是使用 eBPF sockops 中的程序 bpf_sockops 为例, 配合 Linux v6.8 源码探索 eBPF 的工作原理。

BPF 程序操作

load.sh 脚本中,完成了程序的加载和挂载操作,下面的命令使用 bpftool 分别完成 BPF 程序的加载和挂载。

#load
sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"
#attach
sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"

这里 bpftool 是对内核函数 bpf() 封装的命令行工具,用于管理和操作 BPF 程序与 Map。

加载

sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"

命令 bpftool prog loadbpf_sockops.o 加载到路径 /sys/fs/bpf/bpf_sockop 中。

bpftool 对 BPF 程序的加载是由调用 bpf() 指定命令 BPF_PROG_LOAD 并传入 加载选项bpf_prog_load_opts 来完成的:

syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr))
  • syscall bpf() bpf 系统函数
    • __sys_bpf 执行 bpf 命令 BPF_PROG_LOAD
      • bpf_prog_load 为程序分配内存、初始化、检查证书、运行 verifier、创建文件描述符(fd)等

加载成功后的程序,然后就可以进行挂载了。

挂载

sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"

命令 bpftool cgroup attach 将加载(pin 到文件系统中)的程序 /sys/fs/bpf/bpf_sockop 挂载到 cgroup /sys/fs/cgroup/unified/,挂载的类型为 sock_ops。这个 sock_ops 是 bpftool 所使用的库 libbpf 定义,也被是 ELF 部件名,对应着 BPF 程序类型 BPF_PROG_TYPE_SOCK_OPS挂载类型BPF_CGROUP_SOCK_OPS

在 eBPF 编程中,ELF(Executable and Linkable Format)文件用于存储编译后的 eBPF 程序和相关数据。ELF 文件由多个部分(sections)组成,每个部分包含不同类型的信息,比如程序代码、符号表、调试信息等。

libbpf 类型 sock_ops => BPF 程序类型 BPF_PROG_TYPE_SOCK_OPS => 挂载类型 BPF_CGROUP_SOCK_OPS,对应到程序 bpf_sockops.c 中部件名(__section)为 sockops 的代码块。

关于 sock_ops 挂载点:

sock_ops 通常指的是在 Linux 内核中处理套接字操作的一系列函数和操作。

sock_ops 具体可以包括一系列的操作,如创建套接字、绑定套接字到特定地址和端口、监听来自其他套接字的连接请求、接受连接请求、发送和接收数据、以及关闭套接字等。这些操作通常通过一组预定义的 API 来提供,例如 POSIX 套接字 API,它定义了一系列函数,如 socket()bind()listen()accept()send()recv()close() 等,供应用程序调用。

这次 bpftool 是通过 bpf() 执行执行 BPF_PROG_ATTACH 并传入 挂载选项 bpf_prog_attach_opts 来完成的。

syscall(__NR_bpf, BPF_PROG_ATTACH, &attr, sizeof(attr))

cgroup_bpf_enabled_key 特定类型 cgroup BPF 程序的计数器。

!!! 在运行时,会用到该计数器。

到此,我们已经成功将程序挂载到 cgroup 的 sock_ops 上。

套接字操作 sock_ops

套接字的操作很多,这里以连接建立过程中服务端 accept 操作为例。

依然是从系统调用 accept 开始。

BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB 是 socket.accept() 接受连接请求并完成连接建立的操作符,也是众多 sock_ops 操作符 中的一个。这些操作符,可以被看作是 事件 Event,程序的触发则是由事件驱动的。例如:

  • 如果客户端发起连接请求并完成三次握手后的操作符是 BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB
  • 套接字进入监听状态时的操作符是 BPF_SOCK_OPS_TCP_LISTEN_CB
  • 数据被确认 BPF_SOCK_OPS_DATA_ACK_CB
  • TCP 状态改变 BPF_SOCK_OPS_STATE_CB

最后就是 BPF 程序的执行了,不多做赘述,有兴趣的看这里的 分析

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus