温暖的电波  

一 perf buffer专用map简介

在ebpf的perf buffer机制(一)中简单介绍了在使用perf buffer时会先定义一个BPF_MAP_TYPE_PERF_EVENT_ARRAY 类型的map,如下所示:

/* BPF perfbuf map */
struct {
    __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(u32));
} my_map SEC(".maps");

这个map的key_size为sizeof(int),而value_size为sizeof(u32)--但这并非是真正保存在map中的值情况,因为这是一种"FD_ARRAY",map里面存放的其实是自定义的数据类型size,这点后面会讲到。

另外,BPF_MAP_TYPE_PERF_EVENT_ARRAY 类型的map也不需要指定max_entries,因为在libbpf中默认会将max_entries设置为系统中cpu个数。

二 ebpf.o解析map

我们仍然以libbpf-bootstrap为开发框架进行说明。

一个完整的ebpf程序一般由两个部分组成,一个是ebpf部分(prog、map的定义等等),另外一个是用户态部分(解析ebpf部分的.o文件,根据解析的结果创建map、attach prog,与ebpf进行数据交换等等)。

下面我们就开始分析libbpf-bootstrap中maps的创建流程。

2.1 编译生成ebpf.o和skeleton

既然使用率libbpf-bootstrap框架,我们就用框架里面example目录下的minimal这个demo来举例:

  • ebpf程序的编译
clang -O2 -g -Wall -target bpf -c minimal.bpf.c -o minimal.bpf.o

注意:需要确保clang版本在10.0以上

  • 利用bpftool生成skeleton
bpftool gen skeleton minimal.bpf.o > minimal.skel.h

生成的skeleton提取了bpf.o目标文件中的maps和prog信息构造出方便开发的数据结构,同时还对libbpf库函数进行了包装,方便开发者使用。在skeleton生成后可以通过如下几个简单的步骤来完成用户态程序的开发:

  • <name>_bpf__open() – creates and opens BPF application;
  • <name>_bpf__load() – instantiates, loads, and verifies BPF application parts;
  • <name>_bpf__attach() – attaches all auto-attachable BPF programs (it’s optional, you can have more control by using libbpf APIs directly);
  • <name>_bpf__destroy() – detaches all BPF programs and frees up all used resources.

参考:Building BPF applications with libbpf-bootstrap

2.2 解析ebpf.o中"maps"和".maps" section

从2.1我们了解到libbpf-bootstrap已经提供了一个基本的框架skeleton,并提起了btf.o目标文件的内容;接下来就能够通过skeleton提供的libbpf库的封装函数从ebpf.o中解析我们前面在ebpf.c中创建的"maps"或".maps" 。

这一步是通过<name>_bpf__open()函数来完成的,对于前面的minimal的例子,就是minimal_bpf__open。这个函数是对libbpf库函数的封装,简单列举一下这个里面对于map的解析流程。

2.2.1 遍历ebpf.o中的所有sec找到SEC("maps")和SEC(".maps")这两类section

找到名字为"maps"或者".maps"的section,并将它们的idx分别存放到obj->efile.maps_shndx和obj->efile.btf_maps_shndx中

2.2.2 解析SEC("maps")和SEC(".maps")中的数据并将各个map结构内容提取到struct bpf_map

调用btf_object__init_user_maps来解析"maps" section,这个section中存放了所有定义在SEC("maps")的数据结构 struct bpf_map_def

调用bpf_object__init_user_btf_maps来解析".maps" section,这个section中存放了所有定义在SEC(".maps")的数据结构

将这些数据结构都提取到struct bpf_map结构中,后续map在内核中的对象创建时都是基struc bpf_map来进行的。

三 在内核创建ebpf map对象

这里所指的创建ebpf map对象是指在内核中创建。上一节用户态程序调用<name>_bpf__open()函数(底层调用了libbpf中的接口函数)对ebpf.o文件进行了解析,并提取了SEC("maps")和SEC(".maps")两个section中申明的map数据结构。

真正要让ebpf map运行起来,承接起用户态和内核态数据交互等等方面的功能,还需要在内核中创建一个map对象。我们来看一下整个创建流程。

3.1 构建系统调用参数

这一步会枚举前面解析的struct bpf_map_def结构,然后通过bpf系统调用在内核中创建map对象。

首先,对于BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的maps要特殊一些,在定义时一般不用指定max_entries;不指定的话libbpf会调用接口函数map_set_def_max_entries中会默认将max_entries设置为nr_cpus。

我们再来回顾一下它的定义,只定义了map type、key_size, value_size:

/* BPF perfbuf map */
struct {
    __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(u32));
} my_map SEC(".maps");

接着,调用bpf_object__create_map函数将struct bpf_map_def信息提取到union bpf_attr attr中;bpf_attr联合体中集成了bpf系统调用创建map所需要的系数,包括:key_size, value_size, map_type,max_entries等等。

最后,调用syscall(__NR_bpf, BPF_MAP_CREATE, attr, attr_size)系统调用进入内核

3.2 bpf BPF_MAP_CREATE系统调用

syscall__NR_bpf, BPF_MAP_CREATE)在内核中进入如下流程:

SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
err = map_create(&attr);

这里的map_create()函数就是map创建的核心,部分核心代码如下:

map = find_and_alloc_map(attr);
    type = array_index_nospec(type, ARRAY_SIZE(bpf_map_types));    
    ops = bpf_map_types[type];
    map = ops->map_alloc(attr);    //创建struct bpf_map *map对象
err = bpf_obj_name_cpy(map->name, attr->map_name);    
bpf_map_init_memlock(map)     //更新user->locked_vm并检查RLIMIT_MEMLOCK
bpf_map_new_fd(map, f_flags)    //为map创建anon_inode

下面会对这里的这些代码依次展开。

3.3 find_and_alloc_map()创建map对象struct bpf_map

这个步骤是借助内核函数find_and_alloc_map()来完成的。

3.3.1 根据map类型获取对应的map ops

首先根据map的类型attr->map_type找到这种map类型对应的struct bpf_map_ops *ops;  系统中所有map类型对应的bpf_map_ops都存放在struct bpf_map_ops * const bpf_map_types[] 这个数组中。

根据map的类型,经过处理后得到bpf_map_types[]数组的index,即可得到对应的struct bpf_map_ops *指针。对于BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的map,其对应的bpf_map_ops是perf_event_array_map_ops。

这个bpf_map_types数组的具体定义如下:

tatic const struct bpf_map_ops * const bpf_map_types[] = {
#define BPF_PROG_TYPE(_id, _ops)
#define BPF_MAP_TYPE(_id, _ops) \
        [_id] = &_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
#undef BPF_MAP_TYPE
};

3.3.2 创建struct bpf_map结构

3.3.2.1 寻找ops

在2.1获取到map的struct bpf_map_ops指针ops后,内核通过ops->map_alloc(attr)来分配map在内核中的对象struct bpf_map结构,而BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的ops实际上就是perf_event_array_map_ops,其定义如下:

const struct bpf_map_ops perf_event_array_map_ops = {
        .map_alloc_check = fd_array_map_alloc_check,
        .map_alloc = array_map_alloc,   /* map_alloc分配map内存 */
        .map_free = fd_array_map_free,
        .map_get_next_key = array_map_get_next_key,
        .map_lookup_elem = fd_array_map_lookup_elem,
        .map_delete_elem = fd_array_map_delete_elem,
        .map_fd_get_ptr = perf_event_fd_array_get_ptr,
        .map_fd_put_ptr = perf_event_fd_array_put_ptr,
        .map_release = perf_event_fd_array_release,
        .map_check_btf = map_check_no_btf,

};

因此BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的map,ops->map_alloc(attr)实际调用的是array_map_alloc()函数。 

3.3.2.2 分配struct bpf_map

首先将value size进行8字节对齐,本来在定义时的size为4字节,但是实际存储到内核中的map对象的value size是8字节。

elem_size = round_up(attr->value_size, 8);

3.3.2.3 计算map需要的内存空间

对于BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的map,所需要的内存空间如下:

array_size = sizeof(*array); /*struct bpf_array结构的size*/

array_size += (u64) max_entries * elem_size; /*max_entries个value的空间*/

其中struct bpf_array是专门为array类型的map设计的一个数据结构,array类型map的value就存放在此结构中;同时里面内置了bpf_map结构,因此一旦有了bpf_map就能够获取到bpf_array指针:

truct bpf_array {
        struct bpf_map map;   /*内置了bpf_map对象*/
        u32 elem_size;
        u32 index_mask;
        /* 'ownership' of prog_array is claimed by the first program that
         * is going to use this map or by the first program which FD is stored
         * in the map to make sure that all callers and callees have the same
         * prog_type and JITed flag
         */
        enum bpf_prog_type owner_prog_type;
        bool owner_jited;
        union {    /* map元素存放在此 */
                char value[0] __aligned(8);
                void *ptrs[0] __aligned(8);
                void __percpu *pptrs[0] __aligned(8);
        };
};

3.3.2.4 分配内存

这一步是调用array = bpf_map_area_alloc(array_size, numa_node)来完成的。

参数array_size是struct bpf_array的size和map中元素的size之和,这里会通过kmalloc或者vmalloc来分配内存,分配完成后返回的地址既是bpf_array地址,也是bpf_map的地址。

3.4 bpf_map初始化

在3.3分配了bpf_array和bpf_map。这里进行简单的初始化。

/* find_and_alloc_map 完成*/
map->ops = ops;    /* */
map->map_type = type;


bpf_obj_name_cpy(map->name, attr->map_name) /* 拷贝map name */

3.5 创建aone inode

bpf map create系统调用最终返回的是一个文件句柄fd;因此还需要创建一个连接用户态和内核态的连接桥梁,这里是通过anon inode来实现的。

/* 返回map对应的文件句柄 */
int bpf_map_new_fd(struct bpf_map *map, int flags)
{
        int ret;

        ret = security_bpf_map(map, OPEN_FMODE(flags));
        if (ret < 0)
                return ret;
        /* 创建一个anon inode,里面关联了bpf_map ,并返回fd */
        return anon_inode_getfd("bpf-map", &bpf_map_fops, map,
                                flags | O_CLOEXEC);
}

anon_inode_getfd的流程大致如下:

  1) get_unused_fd_flags 寻找一个空闲fd

  2) anon_inode_getfile 分配struct file结构,同时将bpf_map指针存放到file->private_data

  3) 通过fd_install(fd, file)将fd与file关联

  4) 返回fd到用户态

这样后续,用户态就可以通过fd来与map对象进行交互。

四 总结

event buffer对应的map的创建过程如下:
1) 解析ebpf.o中的SEC("maps")和SEC(".maps"),并提取各个maps数据结构作为后续系统调用参数

  与其他maps不同的是,针对这种类型的map会默认将map的max_entry设置为系统cpu个数nr_cpus.

2) 调用syscall(__NR_bpf, BPF_MAP_CREATE, attr, attr_size)系统调用在内核创建maps对象

   创建strcut bpf_array结构,里面内置了struct bpf_map对象,并未为map的value分配内存,这部分内存放在bpf_array中

   为bpf_map对象创建一个anon inode,并将bpf_map对象关联到file->private_data,最后将这个file对应的fd作为bpf sysacall系统调用的返回值返回到用户态

posted on 2023-02-16 22:16  温暖的电波  阅读(1024)  评论(0编辑  收藏  举报