购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1 创建BPF映射

创建BPF映射的最直接方法是使用bpf系统调用。如果该系统调用的第一个参数设置为BPF_MAP_CREATE,则表示创建一个新的映射。该调用将返回与创建映射相关的文件描述符。bpf系统调用的第二个参数是BPF映射的设置,如下所示:


union bpf_attr {
  struct {
    __u32 map_type;     /* one of the values from bpf_map_type */
    __u32 key_size;     /* size of the keys, in bytes */
    __u32 value_size;   /* size of the values, in bytes */
    __u32 max_entries;  /* maximum number of entries in the map */
    __u32 map_flags;    /* flags to modify how we create the map */
  };
}

bpf系统调用的第三个参数是设置属性的大小。

如下代码创建一个键和值为无符号整数的哈希表映射:


union bpf_attr my_map {
  .map_type = BPF_MAP_TYPE_HASH,
  .key_size = sizeof(int),
  .value_size = sizeof(int),
  .max_entries = 100,
  .map_flags = BPF_F_NO_PREALLOC,
};

int fd = bpf(BPF_MAP_CREATE, &my_map, sizeof(my_map));

如果系统调用失败,内核返回-1。失败有三种原因,通过errno来进行区分。如果属性无效,内核将errno变量设置为EINVAL。如果用户没有足够的权限执行操作,内核将errno变量设置为EPERM。最后,如果没有足够的内存保存映射,内核将errno变量设置为ENOMEM。

本章后续将通过示例演示如何使用BPF映射执行一些高级操作。我们先介绍使用更直接的方法创建任何类型的映射。

使用ELF约定创建BPF映射

内核包括一些约定和帮助函数,用于生成和使用BPF映射。使用这些约定比直接执行系统调用更为常用,因为约定的方式更具可读性且更易于遵循。值得注意的是,这些约定即使运行在内核中,底层仍然是通过bpf系统调用来创建映射。如果你事先不知道需要哪种映射类型,那么直接使用bpf系统调用会更加方便。

帮助函数bpf_map_create封装了我们上面使用的代码,可以容易地按需初始化映射。现在,我们只需要一行代码就可以实现上述映射的创建:


int fd;
fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(int), sizeof(int), 100,
    BPF_F_NO_PREALOC);

如果你知道程序将使用的映射类型,也可以预定义映射。这有助于预先对程序使用的映射获取更直观的理解:


struct bpf_map_def SEC("maps") my_map = {
      .type        = BPF_MAP_TYPE_HASH,
      .key_size    = sizeof(int),
      .value_size  = sizeof(int),
      .max_entries = 100,
      .map_flags    = BPF_F_NO_PREALLOC,
};

这种方式使用section属性来定义映射,本示例中为SEC("maps")。这个宏告诉内核该结构是BPF映射,并告诉内核创建相应的映射。

你可能已经注意到,在新的示例中没有看到与映射相关联的文件描述符。这里,内核使用map_data全局变量来保存BPF程序映射信息。这个变量是数组结构,按照程序中指定映射的顺序进行排序。例如,如果上面的映射是程序中的第一个映射,那么该映射的文件描述符可以从数组的第一个元素中获得:


fd = map_data[0].fd;

你也可以从该数组中访问映射的名称和定义。这些信息对于调试和跟踪有时很有用。

映射初始化后,你就可以开始使用它们在内核和用户空间之间传递消息。现在,让我们看看如何使用映射存储数据。 pOEEjT7+FawXNL7KHVPP9RRy1I7d8vh81EVi8h88uxAH2eUyZKMiJgh6H2VATKpE

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开