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_ARRAY
和 BPF_MAP_TYPE_PERCPU_ARRAY
提供通用的数组存储。键类型是一个无符号 32 位整数(4 个字节),并且映射的大小是恒定的。数组的大小在创建时在 max_entries
中定义。创建时,所有数组元素都会被预先分配并初始化为零。BPF_MAP_TYPE_PERCPU_ARRAY
为每个 CPU 使用不同的内存区域,而 BPF_MAP_TYPE_ARRAY
使用相同的内存区域。存储的值可以是任何大小,但是,所有数组元素都对齐到 8 个字节。
自内核 5.5 起,可以通过设置标志 BPF_F_MMAPABLE
为 BPF_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
用于这些映射。