去年学习 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 程序的执行了,不多做赘述,有兴趣的看这里的 分析

关注"云原生指北"公众号

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

云原生指北
25 声望7 粉丝