BPF_MAP_TYPE_CPUMAP

注意

  • BPF_MAP_TYPE_CPUMAP 在内核版本 4.15 中引入

“cpumap” 主要用作 XDP BPF 辅助调用 bpf_redirect_map() 和 XDP_REDIRECT 操作的后端映射,类似于 “devmap”。

与将 XDP 帧重定向到另一个 NIC 设备的 devmap 不同,此映射类型将原始 XDP 帧重定向到另一个 CPU。远程 CPU 将进行 SKB 分配并调用正常的网络堆栈。

此映射类型的一个示例用例是基于软件的接收端缩放 (RSS)。

CPUMAP 表示系统中以映射键为索引的 CPU,映射值为配置设置(每个 CPUMAP 条目)。每个 CPUMAP 条目都有一个专用的内核线程绑定到给定的 CPU,以表示远程 CPU 执行单元。

从 Linux 内核版本 5.9 开始,CPUMAP 可以在远程 CPU 上运行第二个 XDP 程序。 这允许 XDP 程序将其处理拆分到多个 CPU 上。例如,在初始 CPU(看到/接收数据包)需要进行最少的数据包处理的情况下,而远程 CPU(数据包被定向到该 CPU)可以花费更多周期来处理帧。初始 CPU 是执行 XDP 重定向程序的地方。远程 CPU 接收原始的 xdp_frame 对象。

用法

内核 BPF

bpf_redirect_map()

long bpf_redirect_map(struct bpf_map *map, u32 key, u64 flags)

将数据包重定向到 map 在索引 key 处引用的端点。对于 BPF_MAP_TYPE_CPUMAP,此映射包含对 CPU 的引用。

flags 的低两位用作映射查找失败时的返回代码。这样,返回的值就可以是调用者选择的 XDP 程序返回代码之一,直到 XDP_TX

用户空间

注意

CPUMAP 条目只能从用户空间更新/查找/删除,而不能从 eBPF 程序中更新/查找/删除。尝试从内核 eBPF 程序调用这些函数将导致程序加载失败并出现验证器警告。

bpf_map_update_elem()

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

可以使用 bpf_map_update_elem() 辅助函数添加或更新 CPU 条目。 此辅助函数以原子方式替换现有元素。value 参数可以是 struct bpf_cpumap_val

struct bpf_cpumap_val {
    __u32 qsize;  /* queue size to remote target CPU */
    union {
        int   fd; /* prog fd on map write */
        __u32 id; /* prog id on map read */
    } bpf_prog;
};
flags 参数可以是以下之一
  • BPF_ANY: 创建新元素或更新现有元素。

  • BPF_NOEXIST: 仅当新元素不存在时才创建新元素。

  • BPF_EXIST: 更新现有元素。

bpf_map_lookup_elem()

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

可以使用 bpf_map_lookup_elem() 辅助函数检索 CPU 条目。

bpf_map_delete_elem()

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

可以使用 bpf_map_delete_elem() 辅助函数删除 CPU 条目。 此辅助函数在成功时返回 0,在失败时返回负错误。

示例

内核

以下代码片段显示了如何声明一个名为 cpu_mapBPF_MAP_TYPE_CPUMAP,以及如何使用轮询调度方案将数据包重定向到远程 CPU。

struct {
     __uint(type, BPF_MAP_TYPE_CPUMAP);
     __type(key, __u32);
     __type(value, struct bpf_cpumap_val);
     __uint(max_entries, 12);
 } cpu_map SEC(".maps");

 struct {
     __uint(type, BPF_MAP_TYPE_ARRAY);
     __type(key, __u32);
     __type(value, __u32);
     __uint(max_entries, 12);
 } cpus_available SEC(".maps");

 struct {
     __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
     __type(key, __u32);
     __type(value, __u32);
     __uint(max_entries, 1);
 } cpus_iterator SEC(".maps");

 SEC("xdp")
 int  xdp_redir_cpu_round_robin(struct xdp_md *ctx)
 {
     __u32 key = 0;
     __u32 cpu_dest = 0;
     __u32 *cpu_selected, *cpu_iterator;
     __u32 cpu_idx;

     cpu_iterator = bpf_map_lookup_elem(&cpus_iterator, &key);
     if (!cpu_iterator)
         return XDP_ABORTED;
     cpu_idx = *cpu_iterator;

     *cpu_iterator += 1;
     if (*cpu_iterator == bpf_num_possible_cpus())
         *cpu_iterator = 0;

     cpu_selected = bpf_map_lookup_elem(&cpus_available, &cpu_idx);
     if (!cpu_selected)
         return XDP_ABORTED;
     cpu_dest = *cpu_selected;

     if (cpu_dest >= bpf_num_possible_cpus())
         return XDP_ABORTED;

     return bpf_redirect_map(&cpu_map, cpu_dest, 0);
 }

用户空间

以下代码片段显示了如何将 CPUMAP 的 max_entries 动态设置为系统上可用的最大 CPU 数量。

int set_max_cpu_entries(struct bpf_map *cpu_map)
{
    if (bpf_map__set_max_entries(cpu_map, libbpf_num_possible_cpus()) < 0) {
        fprintf(stderr, "Failed to set max entries for cpu_map map: %s",
            strerror(errno));
        return -1;
    }
    return 0;
}

参考