给以下 bpf maps 增加了 BPF_F_CPU 和 BPF_F_ALL_CPUS flag 支持:
- BPF_MAP_TYPE_PERCPU_ARRAY
- BPF_MAP_TYPE_PERCPU_HASH
- BPF_MAP_TYPE_LRU_PERCPU_HASH
- BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
这 2 个 flag 的作用:
lookup_elem() 时,可以通过 BPF_F_CPU + cpu 查询指定 CPU 上的值。
update_elem() 时,可以通过 BPF_F_CPU + cpu 更新指定 CPU 上的值。
update_elem() 时,可以通过 BPF_F_ALL_CPUS 使用一个 value 更新所有 CPU 上的值。
背景
Alexei 和 Andrii 在 review “bpf: Introduce global percpu data” 补丁集时,为了规避
lskel 里 per-CPU maps M:N 问题:在 M 核机器上缓存了长度为 M 的值,在 N 核机器上
update_elem() 时使用了长度为 M 的值,如果 N > M ,则会有问题;提出新增 2 个 flag 的
想法。
实现
利用 flags 参数,如果是 BPF_F_CPU,则将 cpu 嵌入到 flags 高 32 比特。
因此,在内核里,lookup_elem() 时从 flags 里拿到 cpu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// kernel/bpf/arraymap.c
int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 map_flags)
{
// ...
size = array->elem_size;
rcu_read_lock();
pptr = array->pptrs[index & array->index_mask];
if (map_flags & BPF_F_CPU) {
cpu = map_flags >> 32;
copy_map_value(map, value, per_cpu_ptr(pptr, cpu));
check_and_init_map_value(map, value);
goto unlock;
}
// ...
unlock:
rcu_read_unlock();
return 0;
}
|
update_elem() 时,也从 flags 里拿到 cpu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// kernel/bpf/arraymap.c
int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
u64 map_flags)
{
// ...
size = array->elem_size;
rcu_read_lock();
pptr = array->pptrs[index & array->index_mask];
if (map_flags & BPF_F_CPU) {
cpu = map_flags >> 32;
ptr = per_cpu_ptr(pptr, cpu);
copy_map_value(map, ptr, value);
bpf_obj_free_fields(array->map.record, ptr);
goto unlock;
}
// ...
unlock:
rcu_read_unlock();
return 0;
}
|
使用 BPF_F_ALL_CPUS update_elem() 时,复用一个 value:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// kernel/bpf/arraymap.c
int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value,
u64 map_flags)
{
// ...
for_each_possible_cpu(cpu) {
ptr = per_cpu_ptr(pptr, cpu);
val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu;
copy_map_value(map, ptr, val);
bpf_obj_free_fields(array->map.record, ptr);
}
unlock:
rcu_read_unlock();
return 0;
}
|
用法
以 libbpf 为例。
使用 BPF_F_ALL_CPUS update_elem():
1
2
3
4
|
// tools/testing/selftests/bpf/prog_tests/percpu_alloc.c
flags = BPF_F_ALL_CPUS;
err = bpf_map__update_elem(map, keys, key_sz, values, value_sz, flags);
|
使用 BPF_F_CPU + cpu update_elem():
1
2
3
4
|
// tools/testing/selftests/bpf/prog_tests/percpu_alloc.c
flags = (u64)cpu << 32 | BPF_F_CPU;
err = bpf_map__update_elem(map, keys, key_sz, values, value_sz, flags);
|
使用 BPF_F_CPU + cpu lookup_elem():
1
2
3
4
|
// tools/testing/selftests/bpf/prog_tests/percpu_alloc.c
flags = (u64)cpu << 32 | BPF_F_CPU;
err = bpf_map__lookup_elem(map, keys, key_sz, values, value_sz, flags);
|
题外
顺手修复了给 PERCPU HASH maps update_elem() 时泄漏 特殊字段 的问题。
即,如果在 value 里使用了 bpf_timer 这类字段时,update_elem() 不会释放已有的
bpf_timer 字段;要等到 value 被删除、或者整个 map 被销毁时,才会被释放。
小结
其实,该补丁集历经了半年多、一共 13 个版本才合入社区。
在这个过程中,逐渐熟悉了社区的代码品味;期间,也经历了社区 AI review 的建设过程。
痛苦中成长,也收获满满。