ebpf-map概述

1. 简介

BPF Map是驻留在内核中的以键/值方式存储的数据结构,可以被任何知道它们的BPF程序访问。在用户空间运行的程序也可以通过使用文件描述符来访问BPF Map。可以在BPF Map中存储任何类型的数据,只要你事先正确指定数据大小。在内核中,键和值都被视为二进制的方式来存储。

BPF Map用于用户空间和内核空间之间的数据交换、信息传递。
彼此共享MAP的BPF程序不需要具有相同的程序类型,例如,跟踪程序可以与网络程序共享MAP。当前一个BPF程序可以直接访问多达64个不同的MAP。

2. BPF Map类型

头文件定义include/uapi/linux/bpf.h

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,
};

创建过程:

  1. 声明ELF section属性
  2. 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_lockbpf_spin_unlock)来防止竞争条件, 可以在操作映射元素时对访问的映射元素进行锁定, 自旋锁仅适用于数组、哈希、cgroup存储映射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

phantasms

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值