eBPF常见文件介绍

说明

编写eBPF除了对eBPF的机制要要了解之外,还需要一些额外的辅助文件。基本上编写需要的文件已经全部打包在libbpf目录中。
libpf对应的仓库是: https://github.com/libbpf/libbpf

libbpf

libbpf 是一个用于处理 eBPF (Extended Berkeley Packet Filter) 程序的库,它提供了一套 API,用于加载、验证和将 eBPF 程序附加到内核。它也提供了处理 BPF maps 和 BPF 系统调用的功能。
libbpf的作用可以认为是对bpf编程的一种辅助工具,让bpf编程变的简单:只需要编写用户态逻辑和内核态(bpf程序)逻辑,二者之间如何通信,如何在用户态进行配置内核态bpf程序,如何加载bpf程序,卸载bpf程序等都由框架完成,用户态程序仅仅需要调用框架提供的api,并可以直接访问全局变量的方式来控制内核态bpf程序。

常见文件

下面就是libbpf中常见的文件。

  1. bpf_core_read.h: 这个头文件定义了一组宏,这些宏在BPF程序中用于读取内核数据结构的成员。这对于使用CO-RE(Compile Once, Run Everywhere)功能的BPF程序来说非常有用。
  2. bpf_endian.h: 这个头文件定义了一组处理字节序(大端或小端)的宏。在网络编程中,处理字节序是很常见的任务,因此这些宏在eBPF网络程序中经常被使用。
  3. bpf.h: 这个头文件是libbpf库的主要公共接口,它定义了所有用于加载和管理eBPF程序和maps的函数。
  4. bpf_helper_defs.h, bpf_helpers.h: 这两个头文件定义了在eBPF程序中可以使用的各种BPF辅助函数。辅助函数是内核提供的一组函数,它们可以在BPF程序中调用以执行某些特定的任务。
  5. bpf_tracing.h: 这个头文件定义了一组用于创建BPF跟踪程序的宏。
  6. btf.h: 这个头文件定义了操作BTF(BPF Type Format)数据的函数。BTF是一个描述内核数据结构的元数据格式,它使得BPF程序能够更好地理解内核数据。
  7. libbpf_common.h: 这个头文件定义了libbpf库中通用的一些数据结构和函数。
  8. libbpf.h: 这个头文件是libbpf库的主要公共接口,它定义了所有用于加载和管理eBPF程序和maps的函数。
  9. libbpf_legacy.h: 这个头文件包含了一些在新版本的libbpf中已被弃用的函数和数据结构。
  10. libbpf_version.h: 这个头文件定义了libbpf库的版本信息。
  11. skel_internal.h: 这个头文件定义了BPF骨架(skeleton)的内部数据结构。骨架是libbpf生成的一种代码,它为每个BPF程序提供了一个易于使用的C API。
  12. usdt.bpf.h: 这个头文件定义了一组用于处理用户空间静态跟踪点(USDT)的宏和函数。
  13. xsk.h: 这个头文件定义了用于操作AF_XDP套接字的函数。AF_XDP是一种高性能的数据包处理接口,它允许BPF程序直接处理网络设备的数据包。

实际开发过程中,可以根据需要,引入具体的文件.
例如,在ebpfmanager,就只是引入了bpf_helpers.hbpf.h两个头文件,bpf_map.h是由开发者自行定义的.

bpf.h

该文件是Linux内核中BPF (Berkeley Packet Filter) 的一个用户空间库的头文件,它提供了一系列用于操作和管理BPF程序和BPF map的函数接口。下面是其中一些比较重要和常见的函数:

  1. int libbpf_set_memlock_rlim(size_t memlock_bytes);:设置内存锁定限制,这对于创建大型BPF map很有用。
  2. int bpf_map_create(enum bpf_map_type map_type, const char *map_name, __u32 key_size, __u32 value_size, __u32 max_entries, const struct bpf_map_create_opts *opts);:创建一个新的BPF map,指定其类型、名称、键值对的大小以及最大条目数。
  3. int bpf_prog_load(enum bpf_prog_type prog_type, const char *prog_name, const char *license, const struct bpf_insn *insns, size_t insn_cnt, struct bpf_prog_load_opts *opts);:加载一个新的BPF程序到内核。
  4. int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags);:更新BPF map中的一个元素。
  5. int bpf_map_lookup_elem(int fd, const void *key, void *value);:查找BPF map中的一个元素。
  6. int bpf_map_delete_elem(int fd, const void *key);:删除BPF map中的一个元素。
  7. int bpf_map_get_next_key(int fd, const void *key, void *next_key);:获取BPF map中的下一个键。
  8. int bpf_obj_pin(int fd, const char *pathname);:将BPF对象固定到文件系统上的特定路径。
  9. int bpf_obj_get(const char *pathname);:从文件系统上的特定路径获取BPF对象。
  10. int bpf_prog_attach(int prog_fd, int attachable_fd, enum bpf_attach_type type, unsigned int flags);:将BPF程序附加到可附加的文件描述符(例如,网络接口或cgroup)。
  11. int bpf_prog_detach(int attachable_fd, enum bpf_attach_type type);:从可附加的文件描述符卸载BPF程序。
    关于bpf_map_update_elem,bpf_map_lookup_elem,bpf_map_delete_elem 这三个函数,后面也是经常可以看见.

bpf_helpers.h

这个头文件bpf_helpers.h定义了一些用于eBPF程序中的帮助函数和宏。让我们逐行解析其中比较重要和常见的函数和宏:

  1. bpf_printk(fmt, ...):一个宏,用于在eBPF程序中输出调试信息。它使用bpf_trace_printk函数将格式化的字符串打印到内核日志中。
  2. SEC(NAME):一个宏,用于将程序、映射和许可证放置在elf_bpf文件中的不同节(section)中。节名由elf_bpf加载器解释。
  3. bpf_map_lookup_elembpf_map_update_elembpf_map_delete_elem等:这些是函数指针,用于从eBPF程序中调用BPF map相关的操作。它们是通过使用预定义的BPF_FUNC_*宏来初始化的。
  4. bpf_probe_read:一个函数指针,用于从内核空间读取数据到用户空间。它可以帮助eBPF程序读取内存中的数据。
  5. bpf_trace_printk:一个函数指针,用于在eBPF程序中打印调试信息。
  6. load_byteload_halfload_word:这些是用于在eBPF C程序中发出BPF_LD_ABS和BPF_LD_IND指令的LLVM内置函数。
  7. struct bpf_map_def:这是一个结构体,描述了eBPF C程序中的映射属性,以便elf_bpf加载器进行解析。
  8. PT_REGS_PARM1PT_REGS_PARM2等:这些是用于不同架构上获取函数参数和寄存器值的宏。它们根据不同的架构定义了相应的寄存器。
  9. BPF_KPROBE_READ_RET_IPBPF_KRETPROBE_READ_RET_IP:这些是用于在eBPF Kprobe和Kretprobe程序中读取返回指令指针的宏。它们根据不同的架构使用不同的方式来读取返回指令指针。

这些函数和宏提供了一些常见的操作和工具,帮助开发者在eBPF程序中进行调试、访问内核空间、读取数据和操作映射等。
具体文件内容根据不同版本的libbpf会存在差别.随着内核的升高,也会增加更多的函数和宏. 具体的文档可以参见 Helpers

btf.h

BPF (Berkeley Packet Filter) 程序需要进行内核数据结构的访问和操作,但这些数据结构在不同版本的内核中可能会有所不同,甚至在相同版本的内核中,根据配置选项的不同,也可能存在差异。这就导致了 BPF 程序的可移植性问题:一个在特定内核版本和配置下编写的 BPF 程序,在其他内核上可能无法正确运行。
为解决这个问题,引入了 BTF(BPF Type Format)。BTF 是一种新的数据格式,用于在运行时描述内核的数据类型、数据结构以及它们的关系。通过使用 BTF,BPF 程序可以在运行时获取到当前内核的数据结构信息,从而实现对各种内核版本和配置的兼容。
btf.h 文件提供了一套 API ,用于处理 BTF 数据。这些 API 包括创建和释放 BTF 对象、解析 BTF 数据、加载 BTF 到内核、查询 BTF 类型信息等。开发者可以通过这些 API 来处理 BTF 数据,使得 BPF 程序能够在运行时获取到当前内核的数据结构信息,进而实现对内核数据结构的正确访问和操作。
因此,btf.h 能够解决 BPF 程序的可移植性问题,使得 BPF 程序能够在不同版本和配置的内核上运行。vmlinux.h是由btf.h生成的一个头文件,它包含了当前内核所有公开数据结构和类型的定义。这个文件是通过使用BPF工具链中的bpftool从vmlinux二进制文件中提取BTF信息并生成的。生成的vmlinux.h文件可以被BPF程序直接包含,使得该程序能够在编译时获取到当前内核的数据结构信息。

有关btf.h的更多信息,可以参考btf.h中的注释,或者参考下面的链接:
一篇深入解析BTF 实践指南
BPF BTF 详解
BTFGen: 让 eBPF 程序可移植发布更近一步

总结

整体来说,eBPF涉及到的文件还有很多,本篇文章也仅仅只是针对日常经常接触到和使用到的文件进行了简单的介绍,后面如果有机会,会对其他文件进行补充.