__uint, __type, __array, __ulong 这些宏主要用于 eBPF Map 的定义 中,帮助 LLVM 编译器和 BPF CO-RE (Compile Once – Run Everywhere) 理解和提取 eBPF Map 的元数据信息。它们通常与 BPF_MAP_DEF 或 struct 配合使用,在 eBPF 程序中用于告诉内核如何创建 BPF map。
常用的几个宏
1. __uint(name, val)
| |
用途:定义一个名为 name 的字段,其值是一个大小为 val 的整数数组指针。
| |
等价于:
| |
作用:
用于在 eBPF Map 的定义中指定 max_entries 这一属性(即 map 的最大容量)。这种“类型欺骗”的方式让 clang 编译器能保留这些信息在 ELF 文件的 BTF(BPF Type Format)部分里,从而被 bpftool、libbpf 提取并传给内核。
2. __type(name, val)
| |
用途:定义一个名为 name 的字段,其类型是 val 的指针。
| |
等价于:
| |
作用:
告诉内核 key 和 value 类型分别是什么。这对于 map 创建时至关重要,编译器生成的 BTF 信息里会包含 key/value 类型。
3. __array(name, val)
| |
用途:定义一个名为 name 的数组指针,元素类型是 val。
用于某些 map 类型,比如 BPF_MAP_TYPE_PROG_ARRAY,你可能会写:
| |
等价于:
| |
作用:
表示 map 的值是一个函数指针数组或类似结构。这个字段用于描述 array-of-pointer 类型的结构(比如跳转表)。
4. __ulong(name, val)
| |
这个稍微复杂,它依赖另一个宏 ___bpf_concat(通常是这样定义的):
| |
因此:
| |
会展开成:
| |
作用:
- 在不引入实际变量的前提下,让这个
pinning名字能被识别为枚举类型,其值为2。 - 用于设置 map 的属性值,比如
BPF_F_PIN_BY_NAME之类的 enum 值。 __COUNTER__保证名字唯一,防止 enum 名冲突。
这些宏的目的
| 宏名 | 用途 | 实际展开 | 用于定义 eBPF Map 的哪些属性 |
|---|---|---|---|
__uint | 设置整型常量字段 | int (*name)[val] | max_entries, map_flags, type 等 |
__type | 设置类型信息 | typeof(val) *name | key, value 等 |
__array | 表示数组类型 | typeof(val) *name[] | prog_array 类型的值等 |
__ulong | 设置枚举值但不创建变量 | enum { unique_name = val } | pinning, BPF_F_... 选项等 |
🌰 定义一个简单的 Hash Map
| |
my_map 在编译时不会真正分配变量空间,而是借助宏保留元信息给 BTF section,供 libbpf 使用自动加载、创建 map 时提取。
为什么这些宏要被定义为数组指针形式
eBPF 程序中的这些 __uint、__type 定义,本质上是为了将配置元信息嵌入到 BTF (BPF Type Format) 中。
备注也就是说:我们不是在运行时使用这些变量,而是“伪装”为有值的变量,利用它们的类型和结构,在编译阶段生成元数据,供 eBPF 加载器使用。
比如:
| |
展开后:
| |
这并不是真的声明了一个数组,也不会访问它,它只是声明了一个指针,其指向的类型是长度为 1024 的数组,这一信息正好可以被 BTF 捕获并保留。
利用这一点,可以让内核从 BTF 中知道:
max_entries是一个整数类型- 值为
1024
那为什么不直接定义一个整型常量呢?因为 C 语言中:
| |
这种方式会在程序中实际生成一个变量 x 占用空间。eBPF 是一种非常严格的环境,要求尽可能小的指令数和变量空间,而且 map 元数据本质上并不需要运行时访问。
所以我们想让这些“变量”:
- 不真的存在于运行时(不分配空间)
- 但它们的“类型信息”和“值信息”能被保留下来
而使用数组指针这种写法的优点是:
- 不会生成实际变量或初始化语句(编译器优化掉)
val(如1024)被嵌入到了类型中,方便 BTF 保留- 能被 clang/libbpf 识别为元信息字段
- 可以区分不同的字段名(因为是结构体成员)