eBPF Map中的数据成员类型的宏定义

__uint, __type, __array, __ulong 这些宏主要用于 eBPF Map 的定义 中,帮助 LLVM 编译器和 BPF CO-RE (Compile Once – Run Everywhere) 理解和提取 eBPF Map 的元数据信息。它们通常与 BPF_MAP_DEFstruct 配合使用,在 eBPF 程序中用于告诉内核如何创建 BPF map。

常用的几个宏

1. __uint(name, val)

1
#define __uint(name, val) int (*name)[val]

用途:定义一个名为 name 的字段,其值是一个大小为 val 的整数数组指针。

1
__uint(max_entries, 1024);

等价于:

1
int (*max_entries)[1024];

作用:
用于在 eBPF Map 的定义中指定 max_entries 这一属性(即 map 的最大容量)。这种“类型欺骗”的方式让 clang 编译器能保留这些信息在 ELF 文件的 BTF(BPF Type Format)部分里,从而被 bpftool、libbpf 提取并传给内核。

2. __type(name, val)

1
#define __type(name, val) typeof(val) *name

用途:定义一个名为 name 的字段,其类型是 val 的指针。

1
2
__type(key, u32);
__type(value, struct my_val);

等价于:

1
2
u32 *key;
struct my_val *value;

作用:
告诉内核 keyvalue 类型分别是什么。这对于 map 创建时至关重要,编译器生成的 BTF 信息里会包含 key/value 类型。

3. __array(name, val)

1
#define __array(name, val) typeof(val) *name[]

用途:定义一个名为 name 的数组指针,元素类型是 val

用于某些 map 类型,比如 BPF_MAP_TYPE_PROG_ARRAY,你可能会写:

1
__array(values, int);

等价于:

1
int *values[];

作用:
表示 map 的值是一个函数指针数组或类似结构。这个字段用于描述 array-of-pointer 类型的结构(比如跳转表)。

4. __ulong(name, val)

1
#define __ulong(name, val) enum { ___bpf_concat(__unique_value, __COUNTER__) = val } name

这个稍微复杂,它依赖另一个宏 ___bpf_concat(通常是这样定义的):

1
#define ___bpf_concat(a, b) a##b

因此:

1
__ulong(pinning, 2);

会展开成:

1
enum { __unique_value123 = 2 } pinning;

作用:

  • 在不引入实际变量的前提下,让这个 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) *namekey, value
__array表示数组类型typeof(val) *name[]prog_array 类型的值等
__ulong设置枚举值但不创建变量enum { unique_name = val }pinning, BPF_F_... 选项等

🌰 定义一个简单的 Hash Map

1
2
3
4
5
6
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);
    __type(value, long);
} my_map SEC(".maps");

my_map 在编译时不会真正分配变量空间,而是借助宏保留元信息给 BTF section,供 libbpf 使用自动加载、创建 map 时提取。

为什么这些宏要被定义为数组指针形式

eBPF 程序中的这些 __uint__type 定义,本质上是为了将配置元信息嵌入到 BTF (BPF Type Format) 中。

📝 备注

也就是说:我们不是在运行时使用这些变量,而是“伪装”为有值的变量,利用它们的类型和结构,在编译阶段生成元数据,供 eBPF 加载器使用。

比如:

1
__uint(max_entries, 1024);

展开后:

1
int (*max_entries)[1024];

这并不是真的声明了一个数组,也不会访问它,它只是声明了一个指针,其指向的类型是长度为 1024 的数组,这一信息正好可以被 BTF 捕获并保留。

利用这一点,可以让内核从 BTF 中知道:

  • max_entries 是一个整数类型
  • 值为 1024

那为什么不直接定义一个整型常量呢?因为 C 语言中:

1
int x = 123;

这种方式会在程序中实际生成一个变量 x 占用空间。eBPF 是一种非常严格的环境,要求尽可能小的指令数和变量空间,而且 map 元数据本质上并不需要运行时访问。

所以我们想让这些“变量”:

  • 不真的存在于运行时(不分配空间)
  • 但它们的“类型信息”和“值信息”能被保留下来

而使用数组指针这种写法的优点是:

  1. 不会生成实际变量或初始化语句(编译器优化掉)
  2. val(如 1024)被嵌入到了类型中,方便 BTF 保留
  3. 能被 clang/libbpf 识别为元信息字段
  4. 可以区分不同的字段名(因为是结构体成员)
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计