谁在 BPF 中创建地图

who creates map in BPF

阅读 man bpf 和其他一些文档来源后,我的印象是 map 只能由用户进程创建。然而下面的小程序似乎神奇地创建了bpf地图:

struct bpf_map_def SEC("maps") my_map = {
        .type = BPF_MAP_TYPE_ARRAY,
        .key_size = sizeof(u32),
        .value_size = sizeof(long),
        .max_entries = 10,
};

SEC("sockops")
int my_prog(struct bpf_sock_ops *skops)
{
   u32 key = 1;
   long *value;
   ...

   value = bpf_map_lookup_elem(&my_map, &key);
   ...
   return 1;
}

所以我用内核的 tools/bpf/bpftool 加载程序并验证程序是否已加载:

$ bpftool prog show
1: sock_ops  name my_prog  tag f3a3583cdd82ae8d
        loaded_at Jan 02/18:46  uid 0
        xlated 728B  not jited  memlock 4096B

$ bpftool map show
1: array  name my_map  flags 0x0
        key 4B  value 8B  max_entries 10  memlock 4096B

地图当然是空的。但是,从程序中删除 bpf_map_lookup_elem 会导致没有创建地图。

更新 我用 strace 调试了它,发现在这两种情况下,即有 bpf_map_lookup_elem 和没有它,bpftool 确实调用了 bpf(BPF_MAP_CREATE, ...) 并且它显然成功了。然后,如果 bpf_map_lookup_elem 被遗漏,我会在 bpftool map showbpf(BPF_MAP_GET_NEXT_ID, ..) 上立即扫描 returns ENOENT,它永远不会转储地图。所以很明显有些东西没有完成地图创建。

所以我想知道这是否是预期的行为?

谢谢。

I was under impression that a map can be only created by user process.

您完全正确 - 用户程序是调用 bpf 系统调用以加载 eBPF 程序和创建 eBPF 映射的程序。

而你正是这样做的:

So I load the program with tools/bpf/bpftool and ...

您的 bpftool 程序是调用 bpf 系统调用的用户进程,因此是创建 eBPF 映射的用户进程。

BPF 程序不必在创建它的用户程序退出时卸载 - bpftool 可能使用此机制。

一些相关位 from the man page 连接点:

A user process can create multiple maps ... and access them via file descriptors.

Generally, eBPF programs are loaded by the user process and automatically unloaded when the process exits. In some cases ... the program will continue to stay alive inside the kernel even after the process that loaded the program exits.

Each eBPF program is a set of instructions that is safe to run until its completion. ... During verification, the kernel increments reference counts for each of the maps that the eBPF program uses, so that the attached maps can't be removed until the program is unloaded.

正如 antiduh 所解释的,并通过您的 strace 检查确认,bpftool 是在这种情况下创建地图的用户 space 程序。它从 libbpf(在 tools/lib/bpf/ 下)调用函数 bpf_prog_load(),后者最终执行系统调用。然后将程序固定在所需位置(在 bpf 虚拟文件系统挂载点下),以便在 bpftool returns 时不会卸载它。地图未固定。

关于地图创建,魔法位也发生在 libbpf 中。当调用 bpf_prog_load() 时,libbpf 接收目标文件的名称作为参数。 bpftool 不要求加载 this 特定程序或 that 特定地图;相反,它提供目标文件,而 libbpf 必须处理它。于是libbpf中的函数解析了这个ELF目标文件,最终找到了maps和programs对应的一些section。然后它会尝试加载第一个程序。

加载该程序包括以下步骤:

CHECK_ERR(bpf_object__create_maps(obj), err, out);
CHECK_ERR(bpf_object__relocate(obj), err, out);
CHECK_ERR(bpf_object__load_progs(obj), err, out);

换句话说:首先创建我们在目标文件中找到的所有地图。然后执行map重定位(即关联map索引到eBPF指令),最后加载程序指令。

所以关于你的问题:在这两种情况下,有和没有 bpf_map_lookup_elem(),地图都是用 bpf(BPF_MAP_CREATE, ...) 系统调用创建的。之后,发生重定位,如果需要,程序指令会调整为指向新创建的地图。然后,一旦完成所有步骤并加载程序,bpftool 退出。 eBPF 程序应该被固定,并且仍然加载在内核中。据我所知,如果它 确实 使用了映射(如果使用了 bpf_map_lookup_elem()),那么映射仍然被加载的程序引用,并保存在内核中。另一方面,如果程序 使用映射,那么就没有什么可以阻止它们了,所以当 bpftool 持有的文件描述符时映射被销毁关闭,当 bpftool returns.

所以最后,当 bpftool 完成时,如果程序使用它,您会在内核中加载一个映射,但如果没有程序依赖它,则没有映射。在我看来这听起来像是预期的行为;但是,如果您在使用 bpftool 时遇到奇怪的事情,请以某种方式执行 ping,我是该实用程序的工作人员之一。最后一个一般性观察:地图也可以固定并保留在内核中,即使没有程序使用它们,如果需要保留它们的话。