美文网首页
ebpf学习(2)

ebpf学习(2)

作者: android小奉先 | 来源:发表于2024-08-30 18:51 被阅读0次

    本篇介绍

    在使用ebpf时,如何在用户态和内核态传递数据呢? 本篇介绍一个方法,就是使用map.

    map 的基础操作

    创建

    用户态和内核态均可以创建map, 最直接的方法使用bpf系统调用, 不过这个api 并不是posix api, 因此需要通过syscall才行, 如下就是libbpf中的调用方式:

    static inline int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
                  unsigned int size)
    {
        return syscall(__NR_bpf, cmd, attr, size);
    }
    
    

    在bpf.h 中也提供了一个接口, 如下

    LIBBPF_API int bpf_map_create(enum bpf_map_type map_type,
                                  const char *map_name,
                                  __u32 key_size,
                                  __u32 value_size,
                                  __u32 max_entries,
                                  const struct bpf_map_create_opts *opts);
    

    这儿需要注意下,以前的接口名字是bpf_create_map, 甚至一些bpf学习材料也是这个名字, 后来修改成了bpf_map_create, 同时接口参数也变了, 在写代码时需要按照在对应libbpf 库的bpf.h中进行确认.

    bpf_map_type 支持的类型如下:

    enum bpf_map_type {
        BPF_MAP_TYPE_UNSPEC,
        BPF_MAP_TYPE_HASH,
        BPF_MAP_TYPE_ARRAY,
        BPF_MAP_TYPE_PROG_ARRAY,
        BPF_MAP_TYPE_PERF_EVENT_ARRAY,
        BPF_MAP_TYPE_PERCPU_HASH,
        BPF_MAP_TYPE_PERCPU_ARRAY,
        BPF_MAP_TYPE_STACK_TRACE,
        BPF_MAP_TYPE_CGROUP_ARRAY,
        BPF_MAP_TYPE_LRU_HASH,
        BPF_MAP_TYPE_LRU_PERCPU_HASH,
        BPF_MAP_TYPE_LPM_TRIE,
        BPF_MAP_TYPE_ARRAY_OF_MAPS,
        BPF_MAP_TYPE_HASH_OF_MAPS,
        BPF_MAP_TYPE_DEVMAP,
        BPF_MAP_TYPE_SOCKMAP,
        BPF_MAP_TYPE_CPUMAP,
        BPF_MAP_TYPE_XSKMAP,
        BPF_MAP_TYPE_SOCKHASH,
        BPF_MAP_TYPE_CGROUP_STORAGE,
        BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
        BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
        BPF_MAP_TYPE_QUEUE,
        BPF_MAP_TYPE_STACK,
        BPF_MAP_TYPE_SK_STORAGE,
        BPF_MAP_TYPE_DEVMAP_HASH,
        BPF_MAP_TYPE_STRUCT_OPS,
        BPF_MAP_TYPE_RINGBUF,
        BPF_MAP_TYPE_INODE_STORAGE,
        BPF_MAP_TYPE_TASK_STORAGE,
        BPF_MAP_TYPE_BLOOM_FILTER,
    };
    

    map_name名字中不能包含空格,出现空格会被报错误22. opts 用不到的话,写成NULL就行.
    综合上述信息,创建map的例子如下:

      int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);
    

    更新

    在用户态和内核态都可以更新map,对应的方法都是bpf_map_update_elem, 不过内核和用户态的参数会不一样, 因为内核可以直接访问map结构,而用户态只能通过fd的形式. 这儿我们先看下用户态的函数签名:

    LIBBPF_API int bpf_map_update_elem(int fd, const void *key, const void *value,
                                       __u64 flags);
    

    这儿的flags有几种取值:

     *      **BPF_ANY**
     *          Create a new element or update an existing element.
     *      **BPF_NOEXIST**
     *          Create a new element only if it did not exist.
     *      **BPF_EXIST**
     *          Update an existing element.
     *      **BPF_F_LOCK**
     *          Update a spin_lock-ed map element.
    

    查询与删除

    map同样也支持内核和用户态查询, 方法也一样,区别也是内核是直接访问map结构,用户态是访问fd,函数如下:

    LIBBPF_API int bpf_map_lookup_elem(int fd, const void *key, void *value);
    

    删除也同理,用户态函数如下:

    LIBBPF_API int bpf_map_delete_elem(int fd, const void *key);
    

    到了这儿用一个demo演示下map的crud操作, 代码如下

    #include <stdio.h>
    #include <linux/bpf.h>
    //#include <bpf/bpf_helpers.h>
    #include <bpf/bpf.h>
    #include <string.h>
    #include <errno.h>
    
    #ifdef SEC
    #undef SEC
    #endif
    
    #define SEC(NAME) __attribute__((section(NAME), used))
    
    int main() {
      int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);
    
      int key, value, result;
      key = 1, value = 2;
    
      result = bpf_map_update_elem(fd, &key, &value, BPF_ANY);
      printf("map update result %d with bpf any, %s\n", result, strerror(errno));
    
      result = bpf_map_update_elem(fd, &key, &value, BPF_EXIST);
      printf("map update result %d with %d, %s\n", result, value, strerror(errno));
    
      result = bpf_map_lookup_elem(fd, &key, &value);
      printf("map lookup %d value is %d, %s\n", result, value, strerror(errno));
    
      result = bpf_map_delete_elem(fd, &key);  
      printf("map delete %d, %s\n", result, strerror(errno));
      return 0;
    }
    

    这次就按照普通c代码方式编译就行,不需要编译成bpf了.运行效果如下:

    shanks@shanks-ThinkPad-T460s:~/Documents/01code/ebpf/chapter02$ sudo ./a.out 
    map update result 0 with bpf any, Success
    map update result 0 with 2, Success
    map lookup 0 value is 2, Success
    map delete 0, Success
    

    遍历

    bpf也支持遍历map, 不过这个操作只在用户态支持. 另外bpf的遍历和我们常见的遍历不太一样,比如c++的map,我们遍历的话,就是挨个k遍历就行,如果在遍历的时候伴随着删除操作,就需要格外小心,防止迭代器无效.
    而bpf就不用担心这个问题,技巧就在函数签名中:

    LIBBPF_API int bpf_map_get_next_key(int fd, const void *key, void *next_key);
    

    第一个key是开始遍历的k,如果指定一个在map中不存在的k,就会从头遍历. next_key 是下一个将要遍历的k,如果在遍历的时候被删除了,就会导致再次从头遍历. 通过这个机制就不怕遍历的时候删除了.

    我们用一个demo看下:

    #include <stdio.h>
    #include <linux/bpf.h>
    //#include <bpf/bpf_helpers.h>
    #include <bpf/bpf.h>
    #include <string.h>
    #include <errno.h>
    
    #ifdef SEC
    #undef SEC
    #endif
    
    #define SEC(NAME) __attribute__((section(NAME), used))
    
    int main() {
      int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);
    
      int key, value;
      for (key = 1; key < 5; key ++) {
        value = key + 100;
        bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
        printf("insert key %d, value %d\n", key, value);
      }
    
      key = -1;
      value = -1;
      int next_key = 0;
      while(bpf_map_get_next_key(fd, &key, &next_key) == 0) {
        bpf_map_lookup_elem(fd, &key, &value);
        printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
        if (next_key == 4) {
          bpf_map_delete_elem(fd, &next_key);
        }
        key = next_key;
      }
      return 0;
    }
    

    输出如下:

    insert key 1, value 101
    insert key 2, value 102
    insert key 3, value 103
    insert key 4, value 104
    key is -1, value is -1, next_key is 2
    key is 2, value is 102, next_key is 3
    key is 3, value is 103, next_key is 4
    key is 4, value is 103, next_key is 2
    key is 2, value is 102, next_key is 3
    key is 3, value is 103, next_key is 1
    

    查找并删除

    如果需要查找某个值,查找到后就删除这个值,通过前面介绍的方法也可以实现,bpf中也提供了一个方法可以一下子做到,函数如下:

    LIBBPF_API int bpf_map_lookup_and_delete_elem(int fd, const void *key,
                              void *value);
    

    BPF map 类型介绍

    BPF_MAP_TYPE_HASH: 支持动态增删改查
    BPF_MAP_TYPE_ARRAY: 类似与数组,key 就是固定的索引,大小不能修改,也就是不支持删除,支持修改值
    BPF_MAP_TYPE_PROG_ARRAY:用来突破bpf虚拟机指令限制的方法, 在map中存放加载指令的fd.
    BPF_MAP_TYPE_PERF_EVENT_ARRAY:本质上是一个ringbuffer,用来让内核给用户态传递trace数据
    Per-CPU Hash Maps:和BPF_MAP_TYPE_HASH类似,差别在于是per cpu变量,也就是每个cpu一个.
    Per-CPU Array Maps:类似与BPF_MAP_TYPE_ARRAY
    BPF_MAP_TYPE_STACK_TRACE:存储运行进程的堆栈数据
    BPF_MAP_TYPE_CGROUP_ARRAY:用来记录cgroup节点fd,方便共享cgroup信息
    BPF_MAP_TYPE_LRU_HASH and BPF_MAP_TYPE_LRU_PERCPU_HASH: 支持LRU容量管控的hash,不过percpu并不是每个cpu一个hash,而是每个cpu一个lru cache,这样可以让所有cpu 最常用的值得到保留
    BPF_MAP_TYPE_LPM_TRIE:按照LPM(longest prefix match)算法查找最匹配的key
    BPF_MAP_TYPE_ARRAY_OF_MAPS and BPF_MAP_TYPE_HASH_OF_MAPS: 支持存放map的map,不过最多就一层,不支持存放map的map的map
    BPF_MAP_TYPE_DEVMAP:存放网络设备的引用,用来网络包重定向
    BPF_MAP_TYPE_CPUMAP: 也是重来网络包重定向的,不过存放的是cpu的引用
    BPF_MAP_TYPE_XSKMAP:也是网络包重定向,存放的是socket的引用
    BPF_MAP_TYPE_SOCKMAP and BPF_MAP_TYPE_SOCKHASH:也是用来存放打开socket的应用,实现网络包重定向
    BPF_MAP_TYPE_CGROUP_STORAGE and BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE:不同cgroup的逻辑之间协作场景
    BPF_MAP_TYPE_REUSEPORT_SOCKARRAY: 存储可重用的socket引用
    BPF_MAP_TYPE_QUEUE:就是一个queue,此时key为0,只存储value就行
    BPF_MAP_TYPE_STACK:就是一个stack,此时key也是0,只存储value就行

    BPF虚拟文件系统

    用前面的介绍我们可以看到,bpf的map创建成功后返回的是fd,那应该会在文件系统中存在对应的路径,实际上也是. BPF也支持持久化map,这样即使创建这个map的进程退出了,map信息也可以继续保留.
    接下来我们就用demo演示下:
    准备2个代码,一个负责存,一个负责读.
    存map的代码如下:

    
    #include <stdio.h>
    #include <linux/bpf.h>
    #include <bpf/bpf.h>
    #include <string.h>
    #include <errno.h>
    
    static const char *file_path = "/sys/fs/bpf/mapfile";
    
    int main() {
      int fd = bpf_map_create(BPF_MAP_TYPE_HASH, "mymap", sizeof(int), sizeof(int), 100, NULL);
      printf("bpf map create result %d\n", fd);
      int key, value;
      for (key = 1; key < 5; key ++) {
        value = key + 100;
        bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST);
        printf("insert key %d, value %d\n", key, value);
      }
    
      int pinned = bpf_obj_pin(fd, file_path);
      printf("pin ojb at %s, result is %d\n", file_path, pinned);
      return 0;
    }
    

    执行后,结果如下:

    bpf map create result 3
    insert key 1, value 101
    insert key 2, value 102
    insert key 3, value 103
    insert key 4, value 104
    pin ojb at /sys/fs/bpf/mapfile, result is 0
    

    查看对应的路径,也可以看到存在期望的文件节点:

    root@shanks-ThinkPad-T460s:/sys/fs/bpf# ls -al |grep map
    -rw------- 1 root root 0  8月 31 18:39 mapfile
    

    接下来再看下读的代码:

    #include <stdio.h>
    #include <linux/bpf.h>
    #include <bpf/bpf.h>
    #include <string.h>
    #include <errno.h>
    
    static const char *file_path = "/sys/fs/bpf/mapfile";
    
    int main() {
      int fd = bpf_obj_get(file_path);
      printf("bpf obj get %s, fd %d\n", file_path, fd);
      int key, value;
      key = -1;
      value = -1;
      int next_key = 0;
      while(bpf_map_get_next_key(fd, &key, &next_key) == 0) {
        bpf_map_lookup_elem(fd, &key, &value);
        printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
        key = next_key;
      }
      bpf_map_lookup_elem(fd, &key, &value);
      printf("key is %d, value is %d, next_key is %d\n", key, value, next_key);
      return 0;
    }
    

    执行的结果如下:

    bpf obj get /sys/fs/bpf/mapfile, fd 3
    key is -1, value is -1, next_key is 3
    key is 3, value is 103, next_key is 4
    key is 4, value is 104, next_key is 2
    key is 2, value is 102, next_key is 1
    key is 1, value is 101, next_key is 1
    

    相关文章

      网友评论

          本文标题:ebpf学习(2)

          本文链接:https://www.haomeiwen.com/subject/dklfljtx.html