1. 简介
BPF Map
是驻留在内核中的以键/值
方式存储的数据结构,可以被任何知道它们的BPF程序访问。在用户空间运行的程序也可以通过使用文件描述符来访问BPF Map
。可以在BPF Map中存储任何类型的数据,只要你事先正确指定数据大小。在内核中,键和值都被视为二进制的方式来存储。
BPF Map
用于用户空间和内核空间之间的数据交换、信息传递。
彼此共享MAP的BPF程序不需要具有相同的程序类型,例如,跟踪程序可以与网络程序共享MAP。当前一个BPF程序可以直接访问多达64个不同的MAP。
2. BPF 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,
};
根据申请内存方式的不同,BPF Map有很多种类型,当你使用这种类型的BPF Map时,每个CPU都会存储并看到它自己的Map数据,从属于不同CPU之间的数据是互相隔离的,这样做的好处是,在进行查找和聚合操作时更加高效,性能更好,尤其是你的BPF程序主要是在做收集时间序列型数据,如流量数据或指标等。
3. BPF Map操作
3.1 BPF Map创建
3.1.1 系统调用创建
union bpf_attr my_map_attr {
.map_type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 1024,
.map_flags = BPF_F_NO_PREALLOC,
};
int fd = bpf(BPF_MAP_CREATE, &my_map_attr, sizeof(my_map_attr));
上面是通过bpf系统调用函数创建BPF Map的方式,传入的第一个参数是BPF_MAP_CREATE
,第二参数是指定将要创建Map的属性,第三个参数是这个Map配置的大小。因此创建Map之前首先要声明一个BPF Map,其中有下面要素:
- map_type: Map类型
- key_size: Map键大小
- value_size: Map值的大小
- max_entries: Map的元素最大容量
3.1.2 libbpf调用创建
#define SEC(NAME) __attribute__((section(NAME), used))
struct bpf_map_def SEC("maps") my_bpf_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 100,
.map_flags = BPF_F_NO_PREALLOC,
};
创建过程:
- 声明
ELF section
属性 bpf_load
扫描目标文件里定义’maps’ section, 通过BPF系统调用创建BPF Map.
/* parses elf file compiled by llvm .c->.o
* . parses 'maps' section and creates maps via BPF syscall // 就是这里
* . parses 'license' section and passes it to syscall
* . parses elf relocations for BPF maps and adjusts BPF_LD_IMM64 insns by
* storing map_fd into insn->imm and marking such insns as BPF_PSEUDO_MAP_FD
* . loads eBPF programs via BPF syscall
*
* One ELF file can contain multiple BPF programs which will be loaded
* and their FDs stored stored in prog_fd array
*
* returns zero on success
*/
int load_bpf_file(char *path);
SEC("maps")
没有看到与映射相关联的文件描述符。内核使用map_data
全局变量来保存BPF程序映射信息。map_data
是数组结构, 按照程序中指定映射的顺序进行排序。
获取程序中第一个映射的文件描述符, 示例:
fd = map_data[O].fd;
3.2 BPF Map更新
bpf_map_update_elem
来实现保存更新功能。
3.2.1 内核程序
源文件定义kernel/bpf/helpers.c
BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,
void *, value, u64, flags)
3.2.2 用户程序
头文件定义tools/lib/bpf/bpf.h
LIBBPF_API int bpf_map_update_elem(int fd, const void *key, const void *value,
__u64 flags);
3.2.3 区别
内核程序访问的函数与用户程序访问的函数是不同的。
- 内核程序可以直接访问映射,并原子性地更新元素。
- 用户程序需要使用文件描符来引用映射,所以更新操作不是原子性的。
3.3 BPF Map读取
bpf_map_lookup_elem
来实现读取映射元素功能。函数类似于bpf_map_update_elem
.
3.4 BPF Map删除
bpf_map_delete_element
来实现删除映射元素功能。函数类似于bpf_map_update_elem
.
3.5 BPF Map迭代
bpf_map_get_next_key
来实现迭代遍历映射元素功能。函数类似于bpf_map_update_elem
.
3.6 BPF Map查找删除
bpf_map_lookup_and_delete_elem
来实现内核查找和删除映射元素功能。
3.7 BPF Map并发访问
BPF通过BPF自旋锁(bpf_spin_lock
和bpf_spin_unlock
)来防止竞争条件, 可以在操作映射元素时对访问的映射元素进行锁定, 自旋锁仅适用于数组、哈希、cgroup存储映射。