Linux 内核 BPF_MAP_TYPE_XSKMAP用 XDP 将报文零拷贝重定向到 AF_XDP 套接字的完整指南【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文围绕 Linux 内核 BPF 文档Documentation/bpf/map_xskmap.rst展开系统讲解BPF_MAP_TYPE_XSKMAP这一 BPF map 类型的设计原理、关键约束与完整用法。读完本文你将掌握如何声明并挂载一个 XSKMAP、如何在 XDP BPF 程序中通过bpf_redirect_map()把报文直接送进用户态 AF_XDP socketXSK、用户态应如何管理 map 条目以及在内核源码中这条重定向链路的真实落地路径net/xdp/xskmap.c、net/core/filter.c、net/xdp/xsk.c。一、XSKMAP 是什么XDP 到 AF_XDP 的桥梁BPF_MAP_TYPE_XSKMAP自Linux 4.18引入见 Documentation/bpf/map_xskmap.rst 中的版本说明。它作为 XDP BPF 辅助函数bpf_redirect_map()与XDP_REDIRECT动作的后端 map地位与devmap、cpumap并列但目标不同devmap重定向到另一张网卡cpumap重定向到某个 CPU 上的 BPF 程序xskmap把原始 XDP 帧重定向到用户态的 AF_XDP socketXSK报文无需穿越完整的内核网络协议栈实现网卡驱动到用户空间的极低延迟收发。AF_XDP socket 的绑定模型是理解 XSKMAP 的关键一个 AF_XDP socket 绑定到单一 netdev 的单一收包队列queue。XSKMAP 中保存的正是「队列 → socket」的映射关系原文档给出的映射关系图如下--------------------------------------------------- | xsk A | xsk B | xsk C |--- User space | | Queue 0 | Queue 1 | Queue 2 | | Kernel --------------------------------------------------- | | Netdev eth0 | | --------------------------------------------------- | | | | | | key | xsk | | | | --------- | 0 | xsk A | | | | | | ------------- | | | | BPF |-- redirect --| 1 | xsk B | | | | | prog | ------------- | | | | | | 2 | xsk C | | | | | | ------------- | | | --------- | | | | | ---------------------------------------------------必须牢记的队列绑定约束绑定到某个netdev/queue_id的 AF_XDP socket只会接受来自该netdev/queue_id的 XDP 报文。如果 XDP 程序从一个 socket 并未绑定的队列发起重定向报文将不会被该 socket 收到。这是排查「XDP 程序已 redirect 但用户态收不到包」问题时的第一检查点。典型的部署方式是每张 netdev 创建一个 XSKMAPmap 内是一个按队列下标排列的 XSK 文件描述符FD数组max_entries通常设置为该 netdev 支持的队列数。硬性约束原文档与源码共同确认了两条硬约束map 的 key 与 value 大小都必须是 4 字节__u32。源码侧在 net/xdp/xskmap.c 的xsk_map_alloc()中强制执行if (attr-max_entries 0 || attr-key_size ! 4 || attr-value_size ! 4 || attr-map_flags ~(BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY)) return ERR_PTR(-EINVAL);允许的 map flags仅为BPF_F_NUMA_NODE、BPF_F_RDONLY、BPF_F_WRONLY其余取值会在创建 map 时被拒绝。对应地map 在 BPF 程序中的标准声明方式为struct { __uint(type, BPF_MAP_TYPE_XSKMAP); __type(key, __u32); __type(value, __u32); __uint(max_entries, 64); } xsks_map SEC(.maps);二、内核侧数据结构struct xsk_map从源码结构看XSKMAP 的内部实现非常紧凑。定义于 include/net/xdp_sock.hstruct xsk_map { struct bpf_map map; spinlock_t lock; /* Synchronize map updates */ atomic_t count; struct xdp_sock __rcu *xsk_map[]; };xsk_map[]是弹性数组下标即队列 id元素为struct xdp_sock *以RCU 指针方式维护rcu_assign_pointer/rcu_dereference这样 XDP 快路径读表时完全无锁lock自旋锁仅保护条目的增删改配合spin_lock_bh使用count原子计数用于内存记账xsk_map_mem_usage()会把它计入struct xsk_map_node的额外分配。每个被引用的 socket 还会挂一个struct xsk_map_node定义于 net/xdp/xsk.h双向链接到struct xdp_sock的map_list上——即「同一个 XSK 可以被多个 map/多条目引用引用关系可枚举」的设计。这一双向链表在 socket 关闭时用于自动清理 map 中残留的条目xsk_map_try_sock_delete()见下文。此外net/xdp/xskmap.c 注册了xsk_map_ops并通过BTF_ID_LIST_SINGLE(xsk_map_btf_ids, struct, xsk_map)暴露 BTF id使得 BPF 程序与 bpftool 都能以类型安全的方式理解该 map 的内容。三、内核 BPF 侧用法3.1 bpf_redirect_map()long bpf_redirect_map(struct bpf_map *map, u32 key, u64 flags)把当前报文重定向到map中下标key引用的端点。对 XSKMAP 而言key就是目标 socket 绑定的队列 idmap 里存放的是附着在 netdev 各队列上的 XSK 的 FD 引用。重要语义如果 map 在指定下标处为空报文将被丢弃drop。这意味着XDP 程序已加载但 XSKMAP 为空时不会有任何流量能通过该 socket 到达用户态——必须先由用户态把至少一个 XSK 填入 map。在内核中的实际执行路径可以分为两段第一段helper 返回XDP_REDIRECT并暂存目标。由 include/linux/filter.h 中的通用实现__bpf_xdp_redirect_map()完成XSKMAP 通过map_ops.map_redirect即 net/xdp/xskmap.c 中的xsk_map_redirect()接入static long xsk_map_redirect(struct bpf_map *map, u64 index, u64 flags) { return __bpf_xdp_redirect_map(map, index, flags, 0, __xsk_map_lookup_elem); }通用实现的关键逻辑简化摘录ri-tgt_value lookup_elem(map, index); if (unlikely(!ri-tgt_value) !(flags BPF_F_BROADCAST)) { /* lookup 失败清空 redirect 状态返回 flags 低位作为 XDP 动作码 */ ri-map_id INT_MAX; ri-map_type BPF_MAP_TYPE_UNSPEC; return flags action_mask; /* flags 低位可携带 XDP_DROP/XDP_PASS 等默认动作 */ } ... return XDP_REDIRECT;这里体现了原文档「空条目则丢包」的精确机制lookup 失败时helper 返回flags的低位掩码作为 XDP 动作码XDP_ABORTED | XDP_DROP | XDP_PASS | XDP_TX。若调用者传flags 0则返回值为 0即XDP_ABORTED程序也可以选择传入XDP_DROP等动作作为 fallback。第二段XDP 层执行真正的投递。当 XDP 程序以XDP_REDIRECT返回、驱动调用xdp_do_redirect()时net/core/filter.c 中按 map 类型分流XSKMAP 走专用分支__xdp_do_redirect_xsk()→__xsk_map_redirect()定义于 net/xdp/xsk.cint __xsk_map_redirect(struct xdp_sock *xs, struct xdp_buff *xdp) { int err; err xsk_rcv(xs, xdp); if (err) return err; if (!xs-flush_node.prev) { struct list_head *flush_list bpf_net_ctx_get_xskmap_flush_list(); list_add(xs-flush_node, flush_list); } return 0; }值得注意的源码级细节xsk_rcv()会先调用xsk_rcv_check()做校验若 XDP buff 本身就来自 XSK buff poolMEM_TYPE_XSK_BUFF_POOL即 native / zero-copy 模式则走xsk_rcv_zc()直接把 XDP 帧指针转换为用户空间可见的 rx 描述符不产生额外拷贝投递成功后socket 的flush_node被挂到当前 NAPI 上下文的 XSKMAP flush list 上NAPI 收尾时统一调用__xsk_map_flush()→xsk_flush()批量更新 rx 队列的prod指针并唤醒可读状态。这种「批量刷新」是 AF_XDP 高性能的关键之一另有一条 generic 路径当报文以 SKB 形式进入如XDP_GENERIC/ 软件 XDP 场景时xdp_do_generic_redirect_map()对BPF_MAP_TYPE_XSKMAP分支调用xsk_generic_rcv(fwd, xdp)后consume_skb(skb)见 net/core/filter.c 中case BPF_MAP_TYPE_XSKMAP:。3.2 bpf_map_lookup_elem()void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)在 BPF 程序中可用该 helper 取出struct xdp_sock *类型的 XSK 条目引用。但有一条易被忽略的限制用户态侧的 lookup 是sys-only的——原文档说明 XSK 条目只能从用户态更新/删除、不能从 BPF 程序操作实现上net/xdp/xskmap.c 中map_lookup_elem_sys_only被注册为static void *xsk_map_lookup_elem_sys_only(struct bpf_map *map, void *key) { return ERR_PTR(-EOPNOTSUPP); }也就是说从用户态 syscallbpf_map_lookup_elem系统调用直接查询 XSKMAP 条目并不受支持会返回-EOPNOTSUPP。条目管理实际上只能通过 update/delete 完成查询能力保留给内核 BPF 程序使用。另外XSKMAP 实现了map_gen_lookupxsk_map_gen_lookup()当 map 关联了 BTF 类型时验证器会把 BPF 程序中的 lookup 指令直接重写为对xsk_map[]数组的无锁线性读取省去函数调用开销——这也是数组型 map 在 XDP 快路径上的典型优化手段。四、用户态侧用法4.1 bpf_map_update_elem()int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags)用于添加或更新 XSK 条目key该 XSK 所附着队列的queue_id4 字节valueAF_XDP socket 的FD 值4 字节flagsBPF_ANY创建或更新、BPF_NOEXIST仅当条目不存在时创建、BPF_EXIST仅更新已存在条目。底层实现xsk_map_update_elem()net/xdp/xskmap.c的执行要点与文档描述「用 XSK FD 值取回关联的struct xdp_sock实例」完全对应i max_entries→-E2BIGsockfd_lookup(fd)按 FD 找到struct socket校验sock-sk-sk_family PF_XDP否则返回-EOPNOTSUPP即 value 必须是合法的 AF_XDP socket FD校验xs-rx非空rx 队列已建立否则返回-ENOBUFS分配xsk_map_node加自旋锁后按 flags 语义处理BPF_NOEXIST遇已有条目返回-EEXISTBPF_EXIST遇空条目返回-ENOENT相同 socket 重复 update 视为幂等返回 0rcu_assign_pointer(*map_entry, xs)发布新条目并从旧 socket 的map_list中摘除本节点。4.2 bpf_map_lookup_elem()用户态int bpf_map_lookup_elem(int fd, const void *key, void *value)如前所述XSKMAP 的 lookup 被限制为 BPF 程序专用sys-only 版本返回-EOPNOTSUPP。用户态不要依赖此接口读取条目内容。4.3 bpf_map_delete_elem()int bpf_map_delete_elem(int fd, const void *key)删除指定key的 XSK 条目成功返回 0失败返回负错误码。实现xsk_map_delete_elem()用xchg(map_entry, NULL)原子摘除条目并同步从 socket 的map_list清理引用。原文档另提示libxdp 在删除一个 XSK 时会同时把关联的 socket 条目从 XSKMAP 中移除——即用户态的 socket 生命周期与 map 条目是联动的socket 侧的清理逻辑不必再手动 delete。另外内核自身也具备兜底清理socket 关闭路径上的xsk_map_try_sock_delete()会检查该 socket 仍被 map 引用时自动置空对应条目。五、完整示例5.1 内核 BPF 侧声明 XSKMAP 并重定向报文以下示例直接取自原文档Documentation/bpf/map_xskmap.rst展示了xsks_map的声明以及「查表成功才 redirect、否则放行」的推荐写法struct { __uint(type, BPF_MAP_TYPE_XSKMAP); __type(key, __u32); __type(value, __u32); __uint(max_entries, 64); } xsks_map SEC(.maps); SEC(xdp) int xsk_redir_prog(struct xdp_md *ctx) { __u32 index ctx-rx_queue_index; if (bpf_map_lookup_elem(xsks_map, index)) return bpf_redirect_map(xsks_map, index, 0); return XDP_PASS; }逐行解读max_entries为 64即支持最多 64 个队列应不小于网卡队列数ctx-rx_queue_index提供当前收包队列的下标天然满足「socket 绑定队列 报文来源队列」的前提native XDP 下二者一致这是该写法成立的基础先bpf_map_lookup_elem判空再 redirect等价于把「空条目丢包」显式化为XDP_PASS——这样在用户态尚未填入任何 XSK 时流量仍可走协议栈避免上线初期断流bpf_redirect_map()的flags 0lookup 失败场景下的 fallback 动作码为 0XDP_ABORTED由于前面已经预判空表此处实际不会触发。5.2 用户态侧向 XSKMAP 写入 XSK 条目int update_xsks_map(struct bpf_map *xsks_map, int queue_id, int xsk_fd) { int ret; ret bpf_map_update_elem(bpf_map__fd(xsks_map), queue_id, xsk_fd, 0); if (ret 0) fprintf(stderr, Failed to update xsks_map: %s\n, strerror(errno)); return ret; }参数说明queue_id为 u32即 keyxsk_fd为已bind到netdev, queue_id的 AF_XDP socket 的文件描述符即 valueflags 0即BPF_ANY创建或覆盖。结合 net/xdp/xskmap.c 的实现可知该调用的完整语义内核会用这个 FD 反查struct xdp_sock验证其为PF_XDP且 rx 队列就绪后把指针以 RCU 方式发布到xsk_map[queue_id]。典型的完整用户态流程基于原文档指引创建并mmapUMEMbind()AF_XDP socket 到ifname, queue_id对每个队列调用上面的update_xsks_map()把 XSK FD 写入 XSKMAP加载挂有上述 XDP 程序的 XSKMAP例如通过 libxdp 的libbpfAPI 加载收发路径切换为 XDP 快路径报文经xdp_do_redirect()→__xsk_map_redirect()直接落入用户态 rx 队列。关于 AF_XDP socket 的创建细节与转发示例原文档推荐参考 xdp-project 的 bpf-examples 与 libxdp 仓库不在本仓库内本仓库内可直接阅读的内核侧参考文档是 AF_XDP 网络文档 Documentation/networking/af_xdp.rst以及 XSK 数据路径实现 net/xdp/xsk.c、map 实现 net/xdp/xskmap.c。六、常见问题的源码级定位速查现象根因源码依据创建 map 时EINVALkey/value 大小不为 4或使用了不受支持的 map flagsxsk_map_alloc()net/xdp/xskmap.cupdate 返回-EOPNOTSUPPvalue 不是 AF_XDP socket FDsk_family ! PF_XDPxsk_map_update_elem()update 返回-ENOBUFSsocket 尚未完成 rx 队列建立xs-rx为空xsk_map_update_elem()update 返回-E2BIGqueue_id 超出max_entriesxsk_map_update_elem()XDP 已 redirect 但用户态收不到包socket 绑定的netdev/queue_id与报文来源队列不一致或该下标 map 条目为空报文被丢原文档队列绑定说明__bpf_xdp_redirect_map()lookup 失败逻辑用户态bpf_map_lookup_elem系统调用返回-EOPNOTSUPPXSKMAP 的 lookup 仅对内核 BPF 程序开放sys-only 限制xsk_map_lookup_elem_sys_only()socket 关闭后 map 中残留条目内核自动清理xsk_map_try_sock_delete()将条目置空并摘除引用节点net/xdp/xskmap.c七、小结BPF_MAP_TYPE_XSKMAP是 XDP 与 AF_XDP 组合方案中的「路由表」以 4 字节 key队列 id/ 4 字节 valuesocket FD的数组 map 维护「队列 → XSK」映射内核 BPF 程序通过bpf_redirect_map()把原始帧直接投递给用户态NAPI 收尾时批量 flush 完成零拷贝收包条目的增删改完全由用户态掌控且具备 socket 生命周期联动的自动清理。本文所有实现细节均可在 net/xdp/xskmap.c、net/xdp/xsk.c、net/core/filter.c、include/linux/filter.h 与 include/net/xdp_sock.h 中逐行对照验证适合作为搭建用户态 XDP 数据面时的权威参考。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考