BPF_MAP_TYPE_ARRAY 和 BPF_MAP_TYPE_PERCPU_ARRAY

注意

  • BPF_MAP_TYPE_ARRAY 在内核版本 3.19 中引入

  • BPF_MAP_TYPE_PERCPU_ARRAY 在版本 4.6 中引入

BPF_MAP_TYPE_ARRAYBPF_MAP_TYPE_PERCPU_ARRAY 提供通用的数组存储。键类型是一个无符号 32 位整数(4 个字节),并且映射的大小是恒定的。数组的大小在创建时在 max_entries 中定义。创建时,所有数组元素都会被预先分配并初始化为零。BPF_MAP_TYPE_PERCPU_ARRAY 为每个 CPU 使用不同的内存区域,而 BPF_MAP_TYPE_ARRAY 使用相同的内存区域。存储的值可以是任何大小,但是,所有数组元素都对齐到 8 个字节。

自内核 5.5 起,可以通过设置标志 BPF_F_MMAPABLEBPF_MAP_TYPE_ARRAY 启用内存映射。映射定义是页面对齐的,并从第一页开始。分配足够的页面大小和页面对齐的内存块来存储所有数组值,从第二页开始,这在某些情况下会导致内存过度分配。使用此功能的优点是提高了性能和易用性,因为用户空间程序不需要使用辅助函数来访问和修改数据。

用法

内核 BPF

bpf_map_lookup_elem()

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

可以使用 bpf_map_lookup_elem() 辅助函数检索数组元素。此辅助函数返回一个指向数组元素的指针,因此为了避免与用户空间读取值的竞争条件,用户必须在就地更新值时使用诸如 __sync_fetch_and_add() 之类的原语。

bpf_map_update_elem()

long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)

可以使用 bpf_map_update_elem() 辅助函数更新数组元素。

bpf_map_update_elem() 成功时返回 0,失败时返回负错误。

由于数组的大小是恒定的,因此不支持 bpf_map_delete_elem()。要清除数组元素,可以使用 bpf_map_update_elem() 将零值插入该索引。

每个 CPU 数组

存储在 BPF_MAP_TYPE_ARRAY 中的值可以被不同 CPU 上的多个程序访问。要将存储限制为单个 CPU,可以使用 BPF_MAP_TYPE_PERCPU_ARRAY

使用 BPF_MAP_TYPE_PERCPU_ARRAY 时,bpf_map_update_elem()bpf_map_lookup_elem() 辅助函数会自动访问当前 CPU 的插槽。

bpf_map_lookup_percpu_elem()

void *bpf_map_lookup_percpu_elem(struct bpf_map *map, const void *key, u32 cpu)

可以使用 bpf_map_lookup_percpu_elem() 辅助函数查找特定 CPU 的数组值。成功时返回值,如果找不到条目或 cpu 无效,则返回 NULL

并发

自内核版本 5.1 起,BPF 基础架构提供 struct bpf_spin_lock 来同步访问。

用户空间

从用户空间访问使用 libbpf API,其名称与上述相同,使用其 fd 标识映射。

示例

请查看 tools/testing/selftests/bpf 目录以获取功能示例。下面的代码示例演示了 API 用法。

内核 BPF

此代码片段显示如何在 BPF 程序中声明数组。

struct {
        __uint(type, BPF_MAP_TYPE_ARRAY);
        __type(key, u32);
        __type(value, long);
        __uint(max_entries, 256);
} my_map SEC(".maps");

此示例 BPF 程序显示如何访问数组元素。

int bpf_prog(struct __sk_buff *skb)
{
        struct iphdr ip;
        int index;
        long *value;

        if (bpf_skb_load_bytes(skb, ETH_HLEN, &ip, sizeof(ip)) < 0)
                return 0;

        index = ip.protocol;
        value = bpf_map_lookup_elem(&my_map, &index);
        if (value)
                __sync_fetch_and_add(value, skb->len);

        return 0;
}

用户空间

BPF_MAP_TYPE_ARRAY

此代码片段显示如何使用 bpf_map_create_opts 设置标志来创建数组。

#include <bpf/libbpf.h>
#include <bpf/bpf.h>

int create_array()
{
        int fd;
        LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE);

        fd = bpf_map_create(BPF_MAP_TYPE_ARRAY,
                            "example_array",       /* name */
                            sizeof(__u32),         /* key size */
                            sizeof(long),          /* value size */
                            256,                   /* max entries */
                            &opts);                /* create opts */
        return fd;
}

此代码片段显示如何初始化数组的元素。

int initialize_array(int fd)
{
        __u32 i;
        long value;
        int ret;

        for (i = 0; i < 256; i++) {
                value = i;
                ret = bpf_map_update_elem(fd, &i, &value, BPF_ANY);
                if (ret < 0)
                        return ret;
        }

        return ret;
}

此代码片段显示如何从数组中检索元素值。

int lookup(int fd)
{
        __u32 index = 42;
        long value;
        int ret;

        ret = bpf_map_lookup_elem(fd, &index, &value);
        if (ret < 0)
                return ret;

        /* use value here */
        assert(value == 42);

        return ret;
}

BPF_MAP_TYPE_PERCPU_ARRAY

此代码片段显示如何初始化每个 CPU 数组的元素。

int initialize_array(int fd)
{
        int ncpus = libbpf_num_possible_cpus();
        long values[ncpus];
        __u32 i, j;
        int ret;

        for (i = 0; i < 256 ; i++) {
                for (j = 0; j < ncpus; j++)
                        values[j] = i;
                ret = bpf_map_update_elem(fd, &i, &values, BPF_ANY);
                if (ret < 0)
                        return ret;
        }

        return ret;
}

此代码片段显示如何访问数组值的每个 CPU 元素。

int lookup(int fd)
{
        int ncpus = libbpf_num_possible_cpus();
        __u32 index = 42, j;
        long values[ncpus];
        int ret;

        ret = bpf_map_lookup_elem(fd, &index, &values);
        if (ret < 0)
                return ret;

        for (j = 0; j < ncpus; j++) {
                /* Use per CPU value here */
                assert(values[j] == 42);
        }

        return ret;
}

语义

如上例所示,当在用户空间中访问 BPF_MAP_TYPE_PERCPU_ARRAY 时,每个值都是一个包含 ncpus 元素的数组。

调用 bpf_map_update_elem() 时,不能将标志 BPF_NOEXIST 用于这些映射。