openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map

嘟嘟鱼啊鱼
发布于 2022-8-3 18:10
浏览
0收藏

什么是 MAP
map 是驻留在内核空间中的高效键值仓库(key/value store)。map 中的数据可以被 BPF 程序访问,如果想在多个 BPF 程序之间保存状态,可以将状态信息放到 map。map 还可以从用户空间通过文件描述符访问,可以在任意 BPF 程序以及用户空间应用之间共享。Map 就是它们之间的一个桥梁。

openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map-鸿蒙开发者社区

MAP 创建
只能在用户态创建,根据是否用户显式创建,可分为用户态程序创建和加载器创建两种方式。Map 创建后具有全局唯一的 id。BPF_MAP_CREATE 系统调用返回的是一个 BPF Map 的文件描述符。

 

用户态程序创建
用户态程序主动调用 bpf(BPF_MAP_CREATE, …)系统调用,如下 kernel sample 示例:

openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map-鸿蒙开发者社区加载器创建
Bpf 程序里面定义 map,如下:

openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map-鸿蒙开发者社区编译器会创建名字为 maps 的 section,加载器会检查 maps section 下的 map 定义,以 bpftool 为例有如下调用路径帮忙创建 map:

openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map-鸿蒙开发者社区访问 map


用户态访问
用户态访问 map,有统一的系统调用,如下:

 ● int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags); - sys_bpf(BPF_MAP_UPDATE_ELEM, …); // 更新,添加 map entry
 ● int bpf_map_lookup_elem(int fd, const void *key, void *value); - sys_bpf(BPF_MAP_LOOKUP_ELEM, …); // 查找 map entry
 ● int bpf_map_delete_elem(int fd, const void *key); - sys_bpf(BPF_MAP_DELETE_ELEM, …); // 删除 map entry
 ● int bpf_map_get_next_key(int fd, const void *key, void *next_key) - sys_bpf(BPF_MAP_GET_NEXT_KEY, …); // 遍历 map entry
 ● int bpf_map_freeze(int fd) - sys_bpf(BPF_MAP_FREEZE, …); // 设置 frozen 属性

 

BPF 程序访问
访问 bpf 程序,需要借助 bpf helper 函数,功能和上面对应:

● void *bpf_map_lookup_elem(struct bpf_map *map, const void *key) // 查找 map entry
● long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags) // 更新或添加 map entry
● long bpf_map_delete_elem(struct bpf_map *map, const void *key) // 删除 map entry

 

BPF MAP 生命周期
持久化

BPF map 和程序作为内核资源只能通过文件描述符访问,其背后是内核中的匿名 inode,这种文件描述符受限于进程的生命周期,给多个 bpf 程序之间共享 map 带来了诸多不便。例如 tc 或 xdp 环境上,还需要有个常驻的用户态程序;在 data path 多个 hook 点上共享 map(如包统计, 配置等)。

为了解决这个问题,内核实现了一个最小内核空间 BPF 文件系统(https://lore.kernel.org/patchwork/cover/613343/),BPF map 和 BPF 程序 都可以 pin 到这个文件系统内,这个过程称为 object pinning。

相应的,添加了两个新 BPF 系统调用,分别用于钉住(BPF_OBJ_PIN)一个对象和获取(BPF_OBJ_GET)一个被钉住的对象(pinned objects)。

● int bpf_obj_pin(int fd, const char *pathname)
● sys_bpf(BPF_OBJ_PIN, …);
● int bpf_obj_get(const char *pathname)
● fd = sys_bpf(BPF_OBJ_GET, …);

 

生命周期
BPF map 的生命周期通过 refcnt 管理,如下是各种操作对应的 refcnt 的变化。

openEuler Kernel 特性解读 | BPF 数据传递的桥梁 – BPF map-鸿蒙开发者社区BPF MAP 类型
截止到 Linux Kernel 5.13 版本, 已有的 map 类型如下所示:

enum bpf_map_type {
BPF_MAP_TYPE_UNSPEC,
BPF_MAP_TYPE_HASH,
BPF_MAP_TYPE_ARRAY,
BPF_MAP_TYPE_PROG_ARRAY,
BPF_MAP_TYPE_PERF_EVENT_ARRAY,
BPF_MAP_TYPE_PERCPU_HASH,
BPF_MAP_TYPE_PERCPU_ARRAY,
BPF_MAP_TYPE_STACK_TRACE,
BPF_MAP_TYPE_CGROUP_ARRAY,
BPF_MAP_TYPE_LRU_HASH,
BPF_MAP_TYPE_LRU_PERCPU_HASH,
BPF_MAP_TYPE_LPM_TRIE,
BPF_MAP_TYPE_ARRAY_OF_MAPS,
BPF_MAP_TYPE_HASH_OF_MAPS,
BPF_MAP_TYPE_DEVMAP,
BPF_MAP_TYPE_SOCKMAP,
BPF_MAP_TYPE_CPUMAP,
BPF_MAP_TYPE_XSKMAP,
BPF_MAP_TYPE_SOCKHASH,
BPF_MAP_TYPE_CGROUP_STORAGE,
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
BPF_MAP_TYPE_QUEUE,
BPF_MAP_TYPE_STACK,
BPF_MAP_TYPE_SK_STORAGE,
BPF_MAP_TYPE_DEVMAP_HASH,
BPF_MAP_TYPE_STRUCT_OPS,
BPF_MAP_TYPE_RINGBUF,
BPF_MAP_TYPE_INODE_STORAGE,
BPF_MAP_TYPE_TASK_STORAGE,
};

对应 type 的含义及操作是在 include/linux/bpf_types.h 文件中定义的

BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops) …

使用可以参考 samples/bpf/下的示例程序。

 

常见 BPF map 类型

ARRAY Maps:

所有数组 key 为 4 字节,并且不支持删除值

● BPF_MAP_TYPE_ARRAY:简单数组。Key 是数组索引,不能删除元素
● BPF_MAP_TYPE_PERCPU_ARRAY:同上,precpu
● BPF_MAP_TYPE_PROG_ARRAY:bpf_tail_call()用作跳转表的 BPF 程序数组, samples/bpf/sockex3_kern.c
● BPF_MAP_TYPE_PERF_EVENT_ARRAY:内核在 bpf_perf_event_output() 中使用的数组映射,用于将跟踪输出与特定键相关联。用户空间程序将 fds 与每个键关联,并且可以 poll() 这些 fds 以接收数据已被跟踪的通知
● BPF_MAP_TYPE_ARRAY_OF_MAPS:Map in map,外层 map 的 vaule 是内层 map 的 fd,相关用例可以参见内核源代码目录下 samples/bpf/test_map_in_map_kern.c

 

Hash Maps:

● key 的长度没有限制,但显然应该大于 0。
给定 key 查找 value 时,内部通过哈希实现,而非数组索引。
● key/value 是可删除的;作为对比,Array 类型的 map 中,key/value 是不可删除的(但用空值覆盖掉 value ,可实现删除效果)
● BPF_MAP_TYPE_HASH: 最简单的哈希 map
● BPF_MAP_TYPE_PERCPU_HASH: percpu 的 hashmap
● BPF_MAP_TYPE_LRU_HASH:普通 hash map 的问题是有大小限制,超过最大数量后无法再插入了。LRU map 可以避 免这个问题,如果 map 满了,再插入时它会自动将最久未被使用(least recently used)的 entry 从 map 中移除
● BPF_MAP_TYPE_LRU_PERCPU_HASH:percpu 的,同上
● BPF_MAP_TYPE_HASH_OF_MAPS:map-in-map,外层 map 的 value 是内层 map 的 fd


其它类型:

● BPF_MAP_TYPE_STACK_TRACE:内核程序可以通过 bpf_get_stackid() 帮助程序存储堆栈
● BPF_MAP_TYPE_LPM_TRIE:最长前缀匹配,例如,用于存储/检索 IP 路由
● BPF_MAP_TYPE_SOCKMAP:sockmaps 主要用于套接字重定向
● BPF_MAP_TYPE_DEVMAP:与 sockmap 做类似的工作,使用 XDP 的 netdevices 和 bpf_redirect()


添加新的 BPF map 类型
● enum bpf_map_type {} - include/uapi/linux/bpf.h // 在 enum bpf_map_type 添加类型
● BPF_MAP_TYPE(BPF_MAP_TYPE_XXX, yyy_ops) - include/linux/bpf_types.h //注册 ops - BPF_MAP_TYPE 宏,会添加这个 type 到全局 bpf_map_types 数组中
● 合适的位置实现 struct bpf_map_ops yyy_ops
● 允许的 helper 函数 - 在 check_map_func_compatibility 函数中添加兼容的 helper 函数

 

 

 

文章转载自公众号:openEuler

已于2022-8-3 18:10:38修改
收藏
回复
举报
回复
    相关推荐