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()
向该索引插入一个零值。
Per 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
。