传统网络栈处理一个包的完整路径——从驱动到 socket——需要约
2-5 微秒。对于 DDoS
防护、负载均衡、包过滤等场景,大部分包在入口就应该被丢弃或转发,走完整协议栈是巨大的浪费。XDP(eXpress
Data Path)把 eBPF 程序注入到网卡驱动的 NAPI 轮询中,在
sk_buff
分配之前就决定包的命运。这使得单核包处理速度从传统路径的 1-2
Mpps 提升到 24+ Mpps。
但 XDP 不是”旁路”——它是内核网络栈的一部分,与 page pool、NAPI、GRO、TC 紧密集成。理解 XDP 的内核实现,才能正确使用它而不引入诡异的 bug。
本文基于 Linux 6.6/6.8 源码,拆解 XDP 的完整内核实现。
一、核心数据结构
1.1 xdp_buff:XDP 的包描述符
XDP 程序操作的不是 sk_buff,而是更轻量的
xdp_buff:
// include/net/xdp.h
struct xdp_buff {
void *data; // 包数据起始(L2 头)
void *data_end; // 包数据结束
void *data_meta; // 元数据区域(data 之前)
void *data_hard_start; // 帧缓冲区物理起始(含 headroom)
struct xdp_rxq_info *rxq; // 接收队列信息
struct xdp_txq_info *txq; // 发送队列信息(可选)
u32 frame_sz; // 帧总大小
u32 flags; // XDP_FLAGS_HAS_FRAGS 等
};与 sk_buff(约 256
字节,包含路由缓存、conntrack 引用、socket
指针等)相比,xdp_buff 仅 40 字节,这是 XDP
性能优势的结构基础。
内存布局:
data_hard_start data_hard_end
│ │
▼ ▼
┌──────────┬──────────┬────────────┬──────────────────┐
│ headroom │ metadata │ 数据 (L2→) │ skb_shared_info │
└──────────┴──────────┴────────────┴──────────────────┘
▲ ▲ ▲
data_meta data data_end
- headroom:
data_hard_start到data_meta之间,供bpf_xdp_adjust_head()扩展头部 - metadata:
data_meta到data之间,供驱动传递硬件信息(时间戳、RSS 哈希) - skb_shared_info:帧尾部,用于多缓冲区的 fragment 链表
1.2 xdp_frame:传输用帧描述符
当 XDP
决定转发(XDP_TX/XDP_REDIRECT)时,xdp_buff
被转换为 xdp_frame,存储在 headroom 中:
// include/net/xdp.h
struct xdp_frame {
void *data; // 数据指针
u16 len; // 数据长度
u16 headroom; // headroom 大小
u32 metasize; // 元数据大小(低 8 位)
struct xdp_mem_info mem; // 内存模型(page pool/UMEM 等)
struct net_device *dev_rx; // 原始接收设备
u32 frame_sz; // 帧大小
u32 flags; // 标志
};转换通过 xdp_convert_buff_to_frame()
完成,将 xdp_frame 结构体内联写入 headroom
区域,避免额外内存分配。
1.3 xdp_rxq_info:接收队列上下文
// include/net/xdp.h
struct xdp_rxq_info {
struct net_device *dev; // 接收设备
u32 queue_index; // 队列编号
u32 reg_state; // 注册状态
struct xdp_mem_info mem; // 内存模型
unsigned int napi_id; // NAPI 实例 ID
u32 frag_size; // fragment 大小
} ____cacheline_aligned; // cache line 对齐(性能关键)XDP 程序通过 ctx->rxq->dev
获取接收设备信息,通过
ctx->rxq->queue_index 获取队列编号(用于
RSS 感知路由)。
1.4 内存模型枚举
// include/net/xdp.h
enum xdp_mem_type {
MEM_TYPE_PAGE_SHARED = 0, // 页引用计数模型
MEM_TYPE_PAGE_ORDER0, // 独占 order-0 页
MEM_TYPE_PAGE_POOL, // page pool 管理
MEM_TYPE_XSK_BUFF_POOL, // AF_XDP UMEM
};page pool
模型(MEM_TYPE_PAGE_POOL)是现代驱动的首选:XDP_DROP
时页直接回到 page pool,无需走伙伴系统释放。
二、XDP 执行模式
2.1 三种模式对比
| 模式 | 钩子位置 | sk_buff | 性能 | 兼容性 |
|---|---|---|---|---|
| Native | 驱动 NAPI poll | 未分配 | 最高(24+ Mpps) | 需驱动支持 |
| Generic | netif_receive_skb() |
已分配 | 较低(3-5 Mpps) | 所有设备 |
| Offloaded | NIC 固件 | 不涉及 | 线速 | SmartNIC |
2.2 Native XDP:驱动级钩子
Native XDP 通过 ndo_bpf 回调注册:
// include/linux/netdevice.h
struct net_device_ops {
// ...
int (*ndo_bpf)(struct net_device *dev, struct netdev_bpf *bpf);
// ...
};驱动在 NAPI 轮询中执行 XDP 程序:
// 驱动 NAPI poll(概念性,以 ixgbe 风格为例)
static int driver_napi_poll(struct napi_struct *napi, int budget)
{
while (budget > 0) {
// 1. 从 ring buffer 取出 DMA 完成的帧
struct xdp_buff xdp;
xdp.data = page_address(page) + offset;
xdp.data_end = xdp.data + len;
xdp.data_hard_start = page_address(page);
xdp.rxq = &rx_ring->xdp_rxq;
xdp.frame_sz = PAGE_SIZE;
// 2. 执行 XDP 程序
u32 act = bpf_prog_run_xdp(xdp_prog, &xdp);
// 3. 根据返回值处理
switch (act) {
case XDP_DROP:
// 直接回收页到 page pool
page_pool_recycle_direct(pool, page);
break;
case XDP_PASS:
// 分配 sk_buff,走正常协议栈
skb = xdp_build_skb_from_frame(...);
napi_gro_receive(napi, skb);
break;
case XDP_TX:
// 从同一设备发出
driver_xdp_xmit(dev, &xdp);
break;
case XDP_REDIRECT:
// 队列化重定向
xdp_do_redirect(dev, &xdp, xdp_prog);
break;
case XDP_ABORTED:
trace_xdp_exception(dev, xdp_prog, act);
page_pool_recycle_direct(pool, page);
break;
}
budget--;
}
// NAPI 轮询结束时 flush 重定向批次
xdp_do_flush();
return processed;
}关键点:
- XDP 在 sk_buff 分配之前 执行,这是性能优势的根本来源
- XDP_DROP 时仅做页回收(~24ns),比 iptables DROP(~2000ns)快 80 倍
bpf_prog_run_xdp()直接执行 JIT 编译后的机器码
2.3 bpf_prog_run_xdp:程序执行入口
// include/net/xdp.h
static __always_inline u32
bpf_prog_run_xdp(const struct bpf_prog *prog, struct xdp_buff *xdp)
{
u32 act = __bpf_prog_run(prog, xdp, BPF_DISPATCHER_FUNC(xdp));
// 特殊处理:bonding 设备的 XDP_TX
if (static_branch_unlikely(&bpf_master_redirect_enabled_key)) {
if (act == XDP_TX && netif_is_bond_slave(xdp->rxq->dev))
act = xdp_master_redirect(xdp);
}
return act;
}__bpf_prog_run() 调用 JIT
编译后的本地代码。在 x86_64 上,一个简单的 XDP_DROP
程序编译为约 10 条机器指令。
2.4 Generic XDP:通用兼容模式
Generic XDP 在 netif_receive_skb()
之后执行,此时 sk_buff 已经分配:
// net/core/dev.c (概念性)
static int netif_receive_skb_internal(struct sk_buff *skb)
{
// ... 已分配 sk_buff ...
// Generic XDP 执行点
if (static_branch_unlikely(&generic_xdp_needed_key)) {
struct bpf_prog *xdp_prog = rcu_dereference(skb->dev->xdp_prog);
if (xdp_prog) {
// 从 sk_buff 构建临时 xdp_buff
// 执行 XDP 程序
// 根据返回值决定继续或丢弃
}
}
// ... 继续正常路径 ...
}Generic XDP 的局限:
- 性能:sk_buff 已分配,失去了 XDP 的核心优势
- 功能:部分 helper 函数不可用(如
bpf_xdp_adjust_tail()) - 适用场景:驱动不支持 native XDP 时的降级方案
三、五种 XDP 动作路径
3.1 XDP_DROP:极速丢包
case XDP_DROP:
page_pool_recycle_direct(pool, page);
// 或 page_frag_free(data);这是 XDP 最常见的用途——DDoS 防护。页直接回收到 page pool,不触发任何协议栈代码。
性能基准:单核约 24 Mpps(对比 iptables DROP ~3 Mpps)。
3.2 XDP_PASS:递交协议栈
case XDP_PASS:
// xdp_buff → xdp_frame → sk_buff
struct sk_buff *skb = xdp_build_skb_from_frame(xdpf, dev);
if (skb)
napi_gro_receive(napi, skb);xdp_build_skb_from_frame() 的开销约
200-500ns,包括 sk_buff 分配和头部初始化。如果 XDP
程序只是做统计而不过滤,XDP_PASS 的额外开销使得 native XDP
不如不挂载 XDP 程序。
3.3 XDP_TX:原设备回发
case XDP_TX:
// 从同一网卡的 TX 队列发出
xdp_convert_buff_to_frame(&xdp); // 转换为 xdp_frame
driver_xdp_xmit(dev, 1, &xdpf, 0);典型场景:负载均衡器修改 MAC/IP 头后原路返回。注意 XDP_TX 使用接收设备的 TX 队列,可能与正常发包路径竞争锁。
3.4 XDP_REDIRECT:重定向
case XDP_REDIRECT:
xdp_do_redirect(dev, &xdp, xdp_prog);
// 帧暂存在 per-CPU 批次队列中
// NAPI 结束时 xdp_do_flush() 批量发送XDP_REDIRECT 是最灵活的动作,通过 BPF map 指定目标:
| Map 类型 | 目标 | 用途 |
|---|---|---|
BPF_MAP_TYPE_DEVMAP |
其他网卡 | 跨设备转发 |
BPF_MAP_TYPE_DEVMAP_HASH |
其他网卡(哈希索引) | 灵活转发 |
BPF_MAP_TYPE_CPUMAP |
其他 CPU | 负载分摊 |
BPF_MAP_TYPE_XSKMAP |
AF_XDP socket | 用户态收包 |
3.5 XDP_ABORTED:异常处理
case XDP_ABORTED:
trace_xdp_exception(dev, xdp_prog, act);
// 触发 tracepoint,便于调试
page_pool_recycle_direct(pool, page);XDP_ABORTED 在 BPF 程序返回无效值或调用
bpf_throw() 时触发。与 XDP_DROP 的区别是会触发
xdp:xdp_exception tracepoint。
四、重定向机制详解
4.1 批量重定向架构
XDP_REDIRECT 不是立即发送,而是暂存后批量 flush:
// include/net/xdp.h
#define XDP_BULK_QUEUE_SIZE 16
struct xdp_frame_bulk {
int count;
void *xa; // 当前目标设备
void *q[XDP_BULK_QUEUE_SIZE]; // 帧缓冲区
};流程:
包 1 → xdp_do_redirect() → 暂存到 bulk queue
包 2 → xdp_do_redirect() → 暂存到 bulk queue
...
包 N → NAPI 轮询结束
→ xdp_do_flush()
→ 对每个目标设备批量调用 ndo_xdp_xmit()
批量发送的优势:
- 减少 TX 锁竞争:一次锁获取发送多个帧
- 提高 DMA 效率:批量 DMA 映射
- 减少门铃(doorbell)写入:一次通知网卡多个帧
4.2 DEVMAP:跨设备转发
// BPF 程序中
SEC("xdp")
int xdp_redirect_prog(struct xdp_md *ctx)
{
// 查找目标设备
return bpf_redirect_map(&devmap, ctx->rx_queue_index, 0);
}
// 用户态创建 devmap
struct bpf_map_def devmap = {
.type = BPF_MAP_TYPE_DEVMAP,
.key_size = sizeof(u32),
.value_size = sizeof(u32), // 目标 ifindex
.max_entries = 64,
};DEVMAP 支持附加 XDP 程序:每个 map 条目可以关联一个 XDP 程序,在重定向到目标设备前执行(用于修改头部)。
4.3 CPUMAP:跨 CPU 分发
CPUMAP 把 XDP 帧从一个 CPU 转移到另一个 CPU 处理,适用于:
- 单队列网卡需要多核处理
- 重平衡 RSS 分配不均的流
// CPUMAP 内部使用 per-CPU FIFO 队列
// 源 CPU:xdp_do_redirect() → 放入目标 CPU 的队列
// 目标 CPU:kthread 或 NAPI 轮询 → 从队列取帧
// → 可选:执行附加的 XDP 程序
// → xdp_build_skb_from_frame() → 进入协议栈CPUMAP 目标 CPU 上的帧会被转换为 sk_buff
后进入正常协议栈。这意味着 CPUMAP 主要用于把 XDP
前置过滤与后续协议栈处理分摊到不同核。
4.4 XSKMAP:AF_XDP 通道
XSKMAP 把帧重定向到 AF_XDP socket,实现内核到用户态的零拷贝传输。
// include/net/xdp_sock.h
struct xsk_map {
struct bpf_map map;
spinlock_t lock;
atomic_t count;
struct xdp_sock __rcu *xsk_map[];
};五、AF_XDP:用户态高速收包
AF_XDP 是 XDP 生态中最具革命性的组件——它提供了一条从网卡到用户态的零拷贝数据通道,性能可达 100+ Mpps。
5.1 核心架构
// include/net/xdp_sock.h
struct xdp_sock {
struct sock sk; // 内嵌 socket
struct xsk_queue *rx; // 接收描述符环
struct net_device *dev; // 绑定设备
struct xdp_umem *umem; // 用户内存区域
struct xsk_buff_pool *pool; // 缓冲区池
u16 queue_id; // 绑定队列
bool zc; // 零拷贝模式
bool sg; // 散列聚集
struct xsk_queue *tx; // 发送描述符环
u64 rx_dropped; // 丢包计数
u64 rx_queue_full; // 队列满计数
};5.2 UMEM:共享内存区域
// include/net/xdp_sock.h
struct xdp_umem {
void *addrs; // 内存映射地址
u64 size; // 总大小
u32 headroom; // 每帧预留 headroom
u32 chunk_size; // 每帧大小(通常 4096 或 2048)
u32 chunks; // 帧总数
u32 npgs; // 页数
bool zc; // 零拷贝标志
struct page **pgs; // 物理页数组
};UMEM 是用户态和内核共享的内存区域。用户态通过
mmap() 映射,内核(驱动)直接 DMA 写入。
5.3 四环架构
AF_XDP 使用四个无锁环形缓冲区在用户态和内核之间传递帧描述符:
用户态 内核
┌───────────┐ ┌───────────┐
应用填充 │ Fill Ring │ ──空帧描述符─→ │ 驱动 RX │
└───────────┘ └───────────┘
┌───────────┐ ┌───────────┐
应用消费 │ RX Ring │ ←─已填充帧──── │ 驱动 RX │
└───────────┘ └───────────┘
┌───────────┐ ┌───────────┐
应用提交 │ TX Ring │ ──待发送帧──→ │ 驱动 TX │
└───────────┘ └───────────┘
┌───────────┐ ┌───────────┐
应用回收 │Comp. Ring │ ←─已发送帧──── │ 驱动 TX │
└───────────┘ └───────────┘
- Fill Ring:用户态告诉内核”这些帧可以接收数据”
- RX Ring:内核告诉用户态”这些帧已收到数据”
- TX Ring:用户态告诉内核”请发送这些帧”
- Completion Ring:内核告诉用户态”这些帧已发送完”
5.4 零拷贝 vs 拷贝模式
// 零拷贝模式
// 驱动直接 DMA 到 UMEM 页面
// 用户态读取时无数据拷贝
// 要求:驱动实现 ndo_xsk_wakeup
// 性能:100+ Mpps
// 拷贝模式
// 驱动 DMA 到普通页,内核拷贝到 UMEM
// 兼容所有设备
// 性能:20-50 Mpps零拷贝模式需要驱动实现 ndo_xsk_wakeup()
回调,目前支持的主流驱动包括 i40e、ice、mlx5、ixgbe 等。
5.5 AF_XDP 选项
// include/uapi/linux/if_xdp.h
#define XDP_SHARED_UMEM (1 << 0) // 多 socket 共享 UMEM
#define XDP_COPY (1 << 1) // 强制拷贝模式
#define XDP_ZEROCOPY (1 << 2) // 强制零拷贝
#define XDP_USE_NEED_WAKEUP (1 << 3) // 唤醒标志优化
#define XDP_USE_SG (1 << 4) // 散列聚集(多缓冲区)XDP_USE_NEED_WAKEUP
是重要的功率优化:用户态在 Fill Ring 空时不盲目
poll,而是通过 need_wakeup 标志判断是否需要调用
sendto() 唤醒内核。
六、多缓冲区支持
6.1 问题背景
早期 XDP 要求整个包在一个连续帧中(通常 4KB)。这限制了 XDP 无法处理:
- Jumbo frame(9KB+)
- GRO 聚合后的大包
- TSO 产生的大包
6.2 XDP multi-buffer 实现
// include/net/xdp.h
// 通过 flags 标志启用
#define XDP_FLAGS_HAS_FRAGS BIT(0)
// 检测多缓冲区
static inline bool xdp_buff_has_frags(struct xdp_buff *xdp)
{
return !!(xdp->flags & XDP_FLAGS_HAS_FRAGS);
}
// 获取完整长度(含 fragment)
static inline u32 xdp_get_buff_len(struct xdp_buff *xdp)
{
u32 len = xdp->data_end - xdp->data;
if (xdp_buff_has_frags(xdp)) {
struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
len += sinfo->xdp_frags_size;
}
return len;
}
// fragment 存储在帧尾部的 skb_shared_info 中
#define xdp_data_hard_end(xdp) \
((xdp)->data_hard_start + (xdp)->frame_sz - \
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))多缓冲区的 XDP 包由头部帧 + N 个 fragment 组成,fragment
信息复用 skb_shared_info 结构体。BPF 程序通过
helper 函数访问各 fragment 的数据。
七、XDP 元数据
7.1 驱动到 BPF 的元数据传递
驱动可以在 data_meta 区域存储硬件信息,供
XDP 程序读取:
// 驱动设置元数据
xdp.data_meta = xdp.data - sizeof(struct my_metadata);
struct my_metadata *meta = xdp.data_meta;
meta->rx_timestamp = hw_timestamp;
meta->rx_hash = hw_rss_hash;
// BPF 程序读取
SEC("xdp")
int prog(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *meta = (void *)(long)ctx->data_meta;
if (meta + sizeof(struct my_metadata) > data)
return XDP_PASS;
struct my_metadata *m = meta;
// 使用 m->rx_timestamp, m->rx_hash
}7.2 标准元数据操作
// include/net/xdp.h
struct xdp_metadata_ops {
int (*xmo_rx_timestamp)(const struct xdp_md *ctx, u64 *timestamp);
int (*xmo_rx_hash)(const struct xdp_md *ctx, u32 *hash,
enum xdp_rss_hash_type *rss_type);
int (*xmo_rx_vlan_tag)(const struct xdp_md *ctx, __be16 *vlan_proto,
u16 *vlan_tci);
};6.x 内核标准化了元数据接口,驱动通过 kfunc 向 BPF
程序暴露硬件信息。BPF 程序使用
bpf_xdp_metadata_rx_timestamp() 等 kfunc
获取数据,无需了解底层驱动细节。
7.3 BPF 头部调整
// XDP 程序可以调整包头部和元数据区域
bpf_xdp_adjust_head(xdp, delta); // 扩展/收缩头部
bpf_xdp_adjust_meta(xdp, delta); // 扩展/收缩元数据
bpf_xdp_adjust_tail(xdp, delta); // 扩展/收缩尾部bpf_xdp_adjust_head()
是封装/解封装的基础:负 delta 扩展
headroom(添加外层头),正 delta 收缩(剥离头部)。
八、XDP 与内核子系统的集成
8.1 XDP 与 page pool
现代 XDP 驱动使用 page pool 管理帧页面:
page_pool 分配 → DMA 映射 → 网卡 ring buffer
→ DMA 完成 → XDP 程序执行
→ XDP_DROP: page_pool_recycle_direct() // 直接回收
→ XDP_PASS: 页转移给 sk_buff(skb_mark_for_recycle)
→ XDP_TX: 页在 TX 完成后回收
→ XDP_REDIRECT: 页转移给目标设备
XDP_DROP + page pool 组合是性能最优路径:无 sk_buff 分配、无 DMA 解映射、无伙伴系统调用。
8.2 XDP 与 GRO 的关系
时间线:
驱动 NAPI poll
→ 每个包执行 XDP 程序(逐包处理)
→ XDP_PASS → napi_gro_receive()(GRO 聚合)
→ XDP_DROP → 直接回收(跳过 GRO)
XDP 在 GRO 之前执行,这意味着 XDP 程序看到的是原始小包,不是 GRO 聚合后的大包。这既是优势(可以精确过滤单个包)也是限制(无法利用 GRO 的聚合减少处理次数)。
8.3 XDP 与 TC 的关系
收包路径:
XDP(驱动层)→ GRO → TC ingress(qdisc 层)→ Netfilter → 协议栈
发包路径:
协议栈 → Netfilter → TC egress → 驱动 xmit
XDP 在 TC 之前,两者可以配合使用:XDP 做快速过滤,TC BPF 做精细分类。
九、可观测性实战
9.1 XDP tracepoint
# 追踪 XDP 动作分布
bpftrace -e '
tracepoint:xdp:xdp_redirect* {
@redirect[probe] = count();
}
tracepoint:xdp:xdp_exception {
@exception[args->act] = count();
}
interval:s:5 {
print(@redirect);
print(@exception);
clear(@redirect);
clear(@exception);
}
'9.2 追踪 XDP 性能
# 测量 XDP 程序的执行延迟
bpftrace -e '
kprobe:bpf_prog_run_xdp {
@start[tid] = nsecs;
}
kretprobe:bpf_prog_run_xdp /@start[tid]/ {
@xdp_latency_ns = hist(nsecs - @start[tid]);
delete(@start[tid]);
}
interval:s:10 { print(@xdp_latency_ns); clear(@xdp_latency_ns); }
'9.3 AF_XDP 监控
# 查看 AF_XDP socket 统计
cat /proc/net/xsk
# 追踪 AF_XDP 丢包
bpftrace -e '
kprobe:xsk_rcv_check {
@xsk_check = count();
}
kprobe:xsk_drop {
@xsk_drop = count();
}
interval:s:5 {
print(@xsk_check);
print(@xsk_drop);
}
'9.4 XDP 错误诊断
# 追踪 XDP_ABORTED(程序错误)
bpftrace -e '
tracepoint:xdp:xdp_exception {
printf("XDP exception: dev=%s act=%d prog_id=%d\n",
str(args->dev->name), args->act, args->prog_id);
@exceptions = count();
}
'
# 查看 XDP 程序信息
bpftool prog show type xdp
bpftool net show9.5 perf 分析 XDP CPU 开销
# XDP 与协议栈的 CPU 对比
perf record -g -C 0 -- sleep 10
perf report --no-children | head -30
# 观察 bpf_prog_run_xdp 与 netif_receive_skb 的比例十、调优参数速查
| 参数 | 说明 |
|---|---|
| XDP 模式选择 | ip link set dev eth0 xdp obj prog.o(native)ip link set dev eth0 xdpgeneric obj prog.o(generic) |
| AF_XDP 队列 | ethtool -L eth0 combined N 调整队列数 |
| AF_XDP chunk size | UMEM chunk_size(2048 或 4096,影响内存效率) |
| Fill Ring 大小 | 建议 ≥ 2 × batch_size(避免饥饿) |
| 批量大小 | AF_XDP poll 返回批次(默认 64) |
need_wakeup |
开启
XDP_USE_NEED_WAKEUP(减少无效系统调用) |
| IRQ 亲和 | XDP 处理 CPU = 中断 CPU |
性能调优建议
# 1. 确保 native 模式
ip link show dev eth0 | grep xdp
# 应显示 xdp 而非 xdpgeneric
# 2. 绑定 XDP 处理到正确 CPU
# 通过 RSS/IRQ affinity 控制
# 3. 确保 page pool 启用
ethtool -S eth0 | grep page_pool
# 4. AF_XDP 零拷贝确认
# 创建 socket 时指定 XDP_ZEROCOPY
# 如果失败检查驱动支持
# 5. 批量大小优化
# 调整 NAPI weight 和 AF_XDP batch十一、总结
XDP 的性能优势来自三个架构决策:
| 决策 | 传统路径 | XDP |
|---|---|---|
| 包描述符 | sk_buff(256B) | xdp_buff(40B) |
| 分配时机 | 每包分配 sk_buff | 仅 XDP_PASS 时分配 |
| 处理位置 | 协议栈全路径 | 驱动 NAPI 轮询 |
XDP 的五种动作覆盖了网络入口的所有需求:
- XDP_DROP:DDoS 防护(24+ Mpps)
- XDP_PASS:监控/统计后递交协议栈
- XDP_TX:负载均衡器原路返回
- XDP_REDIRECT + DEVMAP:跨设备 L2 转发
- XDP_REDIRECT + XSKMAP:AF_XDP 零拷贝用户态收包
AF_XDP 的四环零拷贝架构进一步打通了内核到用户态的数据通道,使得纯用户态网络应用(如 DPDK 风格但无需绕过内核)成为可能。
参考文献
- Linux 内核源码
include/net/xdp.h(XDP 核心结构体与函数) - Linux 内核源码
include/net/xdp_sock.h(AF_XDP socket 实现) - Linux 内核源码
include/net/xsk_buff_pool.h(XSK 缓冲区池) - Jesper Dangaard Brouer, Toke Høiland-Jørgensen, “XDP — Scalable Packet Processing”, Netdev 2.1, 2017
- Björn Töpel, Magnus Karlsson, “AF_XDP — Scalable User-Space Networking”, Linux Plumbers Conference, 2018
- Toke Høiland-Jørgensen et al., “The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel”, CoNEXT, 2018
上一篇:网络子系统内存管理:sk_buff 分配、page pool 与 NUMA
下一篇:eBPF 网络钩子全景:TC/XDP/socket/cgroup
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Linux 网络子系统深度拆解】eBPF 网络钩子全景:TC/XDP/socket/cgroup
从内核源码全面拆解 eBPF 在网络子系统中的所有挂载点:TC BPF direct-action 模式与 bpf_mprog 多程序链、XDP 驱动级钩子回顾、socket ops 回调与 TCP 生命周期事件、cgroup BPF 策略控制、sk_msg/sk_skb 的 sockmap 重定向引擎、struct_ops 实现自定义拥塞控制,以及 bpftrace 可观测实战。
【Kubernetes 网络深度系列】eBPF 网络子系统的演进:从 hook 到可编程网络操作系统
从 2014 年 socket filter 到可编程拥塞控制和 SmartNIC offload,eBPF 网络能力的完整演化史
【Linux 网络子系统深度拆解】网络丢包定位:从 drop_monitor 到 kfree_skb 追踪
从内核源码拆解 Linux 网络丢包追踪的完整体系:kfree_skb tracepoint 与 80+ 种 drop_reason 枚举、drop_monitor netlink 子系统、dropwatch 工具、perf 丢包记录、bpftrace 丢包聚合脚本,以及生产环境常见丢包点速查表。
【Linux 网络子系统深度拆解】内核网络追踪工具箱:bpftrace/perf/ftrace 实战
从内核 tracepoint 定义出发,系统讲解 bpftrace、perf、ftrace 三大工具在网络诊断中的实战用法:TCP 重传根因分析、softirq 延迟定位、收发包路径延迟剖析、conntrack 表满监控、per-function 火焰图,以及各工具的适用场景与性能开销对比。