如何访问内核程序(内核上下文)中用户程序创建的 bpf 映射?
how to access to bpf map which was made by user program in the kernel program (kernel context)?
假设有两个程序(用户程序和内核程序)。
用户程序通过 api bpf_create_map_name
() 和它 returns fd 制作了 bpf 映射。有了这个 fd,我可以通过系统调用访问地图(例如 bpf_map_update
(fd, ..))。但我只能在用户 space 程序中执行此操作,因为 fd 仅对用户程序(=用户进程)有效,那么 如何在 bpf 程序(位于内核 space)?
我听说我可以通过 libbpf 的 bpf_obj_pin
(fd, 文件路径) 将地图固定在 fs 中,并且可以通过 libbpf 的 bpf_obj_get
(文件路径) 获取这张地图,但问题是bpf_obj_get
仅在用户 space 中可用,因为这是系统调用。
之前看到过类似的讨论()。但这对我来说并不清楚。要在内核space中通过bpf_map_lookup_elem
(fd, ..)访问bpf map,我必须提前知道map的fd。但是正如我之前提到的,地图的fd在内核中是无效的。
我使用的是 libbpf 而不是 BCC。
您可能应该看看 libbpf 的函数 bpf_map__reuse_fd()
,它允许为 BPF 程序重用指向现有映射的文件描述符。
Here is an example using this function:我们首先通过调用bpf_object__find_map_by_name()
获取一个指向要在struct bpf_object
中替换的映射的指针,然后告诉它重用现有的fd。
将映射动态添加到现有 eBPF 程序的唯一方法是使用 map-in-map 类型,如 BPF_MAP_TYPE_ARRAY_OF_MAPS
或 BPF_MAP_TYPE_HASH_OF_MAPS
。为了使用它,我们定义了一个 inner
和 outer
映射类型。用户空间 program/loader 创建 outer
映射并使用对 outer
映射的引用加载程序。之后我们可以从用户空间修改这个 outer
映射,以便随时向它添加一个或多个 inner
映射。
请查看内核中的样本:test_map_in_map_user.c and test_map_in_map_kern.c.
进一步解释并澄清一些事情:
- eBPF 程序不创建映射,ELF 文件包含由用户空间读取的映射定义 program/loader,然后创建它们。 eBPF 程序目前无法创建地图。
- 当加载程序创建映射时,内核returns 一个文件描述符。当一个 eBPF 程序被加载到内核中时,加载器会动态地重写该程序并将这个文件描述符注入其中。内核使用加载的映射来验证程序,之后将映射的实际内存地址而不是文件描述符放在程序中,以便生成的机器代码可以直接通过内存地址访问映射。此验证和转换步骤是程序无法动态“获取”地图引用的原因。
- Map-in-map 类型是动态工作的。这是因为验证程序在加载程序时验证
inner
映射定义以确保代码有效。然后当用户空间更新 map-in-map 时,映射的实际内存地址被存储,可以被 eBPF 程序访问。但即便如此,在加载第一个程序之前,你需要知道内层映射的type/structure。
假设有两个程序(用户程序和内核程序)。
用户程序通过 api bpf_create_map_name
() 和它 returns fd 制作了 bpf 映射。有了这个 fd,我可以通过系统调用访问地图(例如 bpf_map_update
(fd, ..))。但我只能在用户 space 程序中执行此操作,因为 fd 仅对用户程序(=用户进程)有效,那么 如何在 bpf 程序(位于内核 space)?
我听说我可以通过 libbpf 的 bpf_obj_pin
(fd, 文件路径) 将地图固定在 fs 中,并且可以通过 libbpf 的 bpf_obj_get
(文件路径) 获取这张地图,但问题是bpf_obj_get
仅在用户 space 中可用,因为这是系统调用。
之前看到过类似的讨论(bpf_map_lookup_elem
(fd, ..)访问bpf map,我必须提前知道map的fd。但是正如我之前提到的,地图的fd在内核中是无效的。
我使用的是 libbpf 而不是 BCC。
您可能应该看看 libbpf 的函数 bpf_map__reuse_fd()
,它允许为 BPF 程序重用指向现有映射的文件描述符。
Here is an example using this function:我们首先通过调用bpf_object__find_map_by_name()
获取一个指向要在struct bpf_object
中替换的映射的指针,然后告诉它重用现有的fd。
将映射动态添加到现有 eBPF 程序的唯一方法是使用 map-in-map 类型,如 BPF_MAP_TYPE_ARRAY_OF_MAPS
或 BPF_MAP_TYPE_HASH_OF_MAPS
。为了使用它,我们定义了一个 inner
和 outer
映射类型。用户空间 program/loader 创建 outer
映射并使用对 outer
映射的引用加载程序。之后我们可以从用户空间修改这个 outer
映射,以便随时向它添加一个或多个 inner
映射。
请查看内核中的样本:test_map_in_map_user.c and test_map_in_map_kern.c.
进一步解释并澄清一些事情:
- eBPF 程序不创建映射,ELF 文件包含由用户空间读取的映射定义 program/loader,然后创建它们。 eBPF 程序目前无法创建地图。
- 当加载程序创建映射时,内核returns 一个文件描述符。当一个 eBPF 程序被加载到内核中时,加载器会动态地重写该程序并将这个文件描述符注入其中。内核使用加载的映射来验证程序,之后将映射的实际内存地址而不是文件描述符放在程序中,以便生成的机器代码可以直接通过内存地址访问映射。此验证和转换步骤是程序无法动态“获取”地图引用的原因。
- Map-in-map 类型是动态工作的。这是因为验证程序在加载程序时验证
inner
映射定义以确保代码有效。然后当用户空间更新 map-in-map 时,映射的实际内存地址被存储,可以被 eBPF 程序访问。但即便如此,在加载第一个程序之前,你需要知道内层映射的type/structure。