mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
5610 字
16 分钟
XDP 与 eBPF 高性能网络
2025-05-24

2020 年,Cloudflare 在应对大规模 DDoS 攻击时,将 XDP 程序部署在网卡驱动层,实现了每秒数亿包的线速丢弃——而此时数据包甚至还没有进入内核协议栈。XDP 与 eBPF 的组合,让高性能网络处理不再必须绕过内核。

一、引言:为什么 XDP 是内核态的快速路径#

第 2 章:内核旁通技术全景中,我们系统梳理了绕过内核协议栈的多种技术路线——从 DPDK 的完全旁通到 io_uring 的异步 I/O 加速。这些方案各有取舍:DPDK 性能极致但独占 CPU 核心且放弃内核生态,io_uring 降低了系统调用开销但仍需穿越完整协议栈。

XDP(eXpress Data Path)提供了一条中间路线:它不离开内核,而是在网卡驱动收包之后、内核协议栈处理之前,插入一个可编程的快速路径。这意味着:

  • 无上下文切换:XDP 程序运行在内核态,不需要用户态/内核态切换
  • 无用户态拷贝:数据包不离开内核空间,不需要 copy_to_user / copy_from_user
  • 无 sk_buff 开销:XDP 在 sk_buff 分配之前执行,避免了协议栈最重的数据结构创建
  • 可编程:通过 eBPF 字节码,你可以定义任意的包处理逻辑

[!NOTE] XDP 的核心洞察是:绝大多数数据包不需要穿越完整协议栈。DDoS 攻击包可以直接丢弃,负载均衡包可以直接转发,只有少数包才需要交给 TCP/IP 协议栈处理。XDP 让你在最早的时机做出这个决策。

一、eBPF 架构基础#

XDP 的可编程性建立在 eBPF(extended Berkeley Packet Filter)之上。在深入 XDP 之前,必须先理解 eBPF 的架构——它是整个技术栈的根基。

BPF 指令集#

eBPF 定义了一套精简的 RISC 指令集,具有以下特征:

  • 10 个 64 位通用寄存器r0 ~ r9,其中 r0 存放返回值,r1 ~ r5 传递函数参数,r6 ~ r9 在函数调用间被 callee 保存
  • 1 个只读帧指针r10,指向当前栈帧顶部,用于访问栈上局部变量
  • 定长指令:每条指令 8 字节,格式为 op:8 dst:4 src:4 off:16 imm:32
  • 64 位语义:所有寄存器都是 64 位宽,32 位子寄存器通过 w0 ~ w9 访问
// BPF 指令示例(手动编写极少,通常由 clang 编译 C 生成)
// r0 = r1 + 5
// op = BPF_ALU64 | BPF_ADD | BPF_K (0x07)
// dst = r0, src = r1, off = 0, imm = 5
// if r1 > 10 goto +2
// op = BPF_JMP | BPF_JGT | BPF_K (0x25)
// dst = r1, off = 2, imm = 10

[!NOTE] 实际开发中,你不会手写 BPF 指令。使用 clang 将 C 代码编译为 BPF 字节码(clang -target bpf -O2 -c prog.c -o prog.o),然后通过 bpf() 系统调用加载到内核。

验证器(Verifier):eBPF 的安全守门人#

eBPF 程序在内核中运行,如果不受约束,一个恶意或有 bug 的 BPF 程序可能导致内核崩溃。验证器(verifier)是 eBPF 安全模型的核心,它在程序加载时进行静态分析,确保程序满足以下约束:

  1. 有界循环:程序必须在有限步内终止。验证器通过展开循环(unroll)或限制迭代次数来保证。Linux 5.3+ 支持有界循环,但迭代上限必须可静态确定
  2. 合法内存访问:所有指针解引用必须经过边界检查。验证器追踪每个指针的可能范围(bounded pointer),确保不会越界读写
  3. 无内核指针泄漏:BPF 程序不能将内核地址泄露到用户空间。返回值和 map 值不能包含内核指针
  4. 无未初始化读取:所有寄存器和栈变量在读取前必须被初始化
  5. 指令数限制:早期限制为 4096 条指令,Linux 5.2+ 提升到 100 万条(通过 bpf_subprog 调用链可达 100 万)

验证器使用深度优先搜索(DFS)遍历程序的控制流图(DAG),对每条可能的执行路径进行状态追踪:

graph LR A[BPF 字节码] --> B[验证器<br>DFS 遍历 DAG] B -->|通过验证| C[JIT 编译器] B -->|验证失败| D[拒绝加载<br>返回错误信息] C --> E[原生机器码] E --> F[内核 Hook 点执行]

验证器的核心算法:

对于每条执行路径:
1. 维护寄存器状态(类型、范围、是否已初始化)
2. 维护栈帧状态(每个栈槽的类型和值)
3. 在分支点保存状态快照
4. 合并汇聚点的状态(取交集)
5. 检测不可达代码
6. 限制总探索状态数(防止状态爆炸)

[!WARNING] 验证器的错误信息有时晦涩难懂。当程序被拒绝时,关注 invalid mem accessunreachable instructionback-edge in program 等关键提示。使用 llvm-objdump -S prog.o 查看生成的 BPF 指令有助于定位问题。

JIT 编译:从字节码到原生代码#

验证通过后,BPF 字节码由内核的 JIT(Just-In-Time)编译器翻译为当前架构的原生机器码:

  • x86_64:翻译为 x86-64 指令,利用寄存器映射(BPF r0-r5 → x86 rax, rdi, rsi, rdx, rcx, r8)
  • ARM64:翻译为 AArch64 指令
  • RISC-V:翻译为 RV64G 指令

JIT 编译带来的性能提升是显著的——原生代码执行比解释执行快 2-5 倍。内核还支持 BPF to BPF 调用(子函数),避免代码膨胀。

# 查看 JIT 编译状态
cat /proc/sys/net/core/bpf_jit_enable
# 0 = 禁用, 1 = 启用, 2 = 启用 + 添加调试跟踪
# 启用 JIT
sudo sysctl -w net.core.bpf_jit_enable=1
# 查看 JIT 编译后的指令(需要 bpf_jit_enable=2)
sudo cat /proc/sys/net/core/bpf_jit_kallsyms

BPF 程序类型#

eBPF 不只是网络包处理——它可以挂载到内核的多个 Hook 点:

程序类型Hook 点典型用途
BPF_PROG_TYPE_XDP网卡驱动收包路径DDoS 防护、负载均衡、防火墙
BPF_PROG_TYPE_SCHED_CLStc(流量控制)分类器流量整形、包标记
BPF_PROG_TYPE_SOCKET_FILTERsocket 收包过滤tcpdump/libpcap 抓包
BPF_PROG_TYPE_CGROUP_SKBcgroup 网络过滤容器网络策略
BPF_PROG_TYPE_CGROUP_SOCKcgroup socket 操作socket 创建/连接控制
BPF_PROG_TYPE_KPROBE内核函数入口/返回内核追踪、性能分析
BPF_PROG_TYPE_TRACEPOINT内核 tracepoint系统调用追踪
BPF_PROG_TYPE_PERF_EVENT性能计数器CPU 火焰图、缓存命中分析

[!NOTE] XDP 是 eBPF 在网络领域最重要的应用,但 eBPF 的能力远超网络。本系列第 2 章中提到的可编程数据路径,其核心就是 eBPF 提供的内核可编程能力。

eBPF 处理流水线#

将上述组件串联,eBPF 的完整处理流水线如下:

graph TB subgraph 用户空间 A[BPF C 源码] --> B["clang -target bpf<br>编译为 BPF 字节码"] B --> C["bpf() 系统调用<br>加载到内核"] end subgraph 内核空间 C --> D[验证器 Verifier] D -->|验证通过| E[JIT 编译器] D -->|验证失败| F[拒绝加载] E --> G[原生机器码] G --> H[挂载到 Hook 点] H --> I[事件触发执行] I --> J[BPF Map 读写] J --> K[返回动作/结果] end subgraph 辅助基础设施 L[BPF Map<br>数据共享] -.-> J M[Helper 函数<br>内核辅助] -.-> I end style D fill:#ffcdd2,stroke:#c62828 style E fill:#c8e6c9,stroke:#2e7d32 style L fill:#bbdefb,stroke:#1565c0

二、XDP:网卡驱动的快速路径#

XDP Hook 点:为什么位置决定一切#

XDP 的性能优势源于它在网络收包路径中的位置。理解这个位置,就理解了 XDP 的一切:

graph TB A[网卡 NIC] -->|DMA 写入| B[驱动接收环形缓冲区<br>rx_ring] B -->|XDP Hook 点| C[XDP 程序执行] C -->|XDP_PASS| D[构建 sk_buff] D --> E[netif_receive_skb] E --> F[IP 层 ip_rcv] F --> G[TCP/UDP 层] G --> H[Socket 缓冲区] H --> I[用户空间 read] C -->|XDP_DROP| J[直接丢弃<br>无 sk_buff 开销] C -->|XDP_TX| K[原网卡发回<br>无 sk_buff 开销] C -->|XDP_REDIRECT| L[重定向到<br>CPUMAP/DEVMAP/XSKMAP] style C fill:#ff9800,stroke:#e65100,color:#fff style J fill:#ef5350,stroke:#c62828,color:#fff style K fill:#66bb6a,stroke:#2e7d32,color:#fff style L fill:#42a5f5,stroke:#1565c0,color:#fff B -.->|关键:在此之后<br>sk_buff 之前| C

关键点:XDP 程序在 DMA 完成之后、sk_buff 分配之前执行。这意味着:

  • 数据包还在 rx_ring 的原始 DMA 缓冲区中,以 xdp_buff 结构表示
  • 没有创建 sk_buff(这是协议栈最重的数据结构之一,分配开销约 200-400ns)
  • 没有解析任何协议头(XDP 程序自己按需解析)
  • 没有路由查找、没有 Netfilter 钩子、没有 conntrack

[!WARNING] XDP 有三种运行模式,性能差异显著:

  • 原生模式(native/XDP):Hook 在驱动中,性能最优。需要网卡驱动支持
  • 通用模式(generic/SKB):Hook 在 netif_receive_skb 之后,兼容所有网卡但性能较低
  • 卸载模式(offload):BPF 程序运行在智能网卡(如 Netronome)上,性能最高但硬件依赖强

生产环境务必使用原生模式。通用模式仅用于开发调试。

XDP 的五种动作#

XDP 程序的返回值决定了数据包的命运:

返回值常量含义性能影响
0XDP_PASS正常交给协议栈处理最慢(需创建 sk_buff)
1XDP_DROP在驱动层直接丢弃最快(零开销)
2XDP_REDIRECT重定向到其他 CPU/网卡/套接字快(避免协议栈)
3XDP_TX从同一网卡发回快(无需路由查找)
4XDP_ABORTED异常丢弃,触发 tracepoint用于调试

各动作的典型应用场景:

  • XDP_DROP:DDoS 防护——在驱动层丢弃恶意流量,不消耗任何协议栈资源
  • XDP_TX:无状态负载均衡——修改源/目标 MAC 后原路发回(L2 负载均衡)
  • XDP_REDIRECT:有状态负载均衡——将包重定向到后端服务器所在网卡或 CPU
  • XDP_PASS:正常流量——交给内核协议栈处理

XDP 程序结构#

一个典型的 XDP 程序结构如下:

// xdp_prog_kern.c — XDP 程序(运行在内核态)
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// 定义一个 BPF Map 用于统计
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 5); // 对应 5 种 XDP 动作
__type(key, __u32);
__type(value, __u64);
} xdp_stats_map SEC(".maps");
// XDP 程序入口
SEC("xdp")
int xdp_stats_func(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
// 解析以太网头
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS; // 包太短,交给协议栈
// 只处理 IPv4
if (eth->h_proto != __builtin_bswap16(0x0800))
return XDP_PASS;
// 解析 IP 头
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
// 统计各动作计数
__u32 action = XDP_PASS; // 默认动作
__u64 *count = bpf_map_lookup_elem(&xdp_stats_map, &action);
if (count)
(*count)++;
return action;
}
char _license[] SEC("license") = "GPL";

struct xdp_md 是 XDP 程序的上下文,定义在内核头文件中:

// include/uapi/linux/bpf.h(简化)
struct xdp_md {
__u32 data; // 数据包起始地址(可读写)
__u32 data_end; // 数据包结束地址(只读)
__u32 data_meta; // 元数据区域起始
__u32 ingress_ifindex; // 入接口索引
__u32 rx_queue_index; // 接收队列索引
};

[!NOTE] XDP 程序中访问数据包的方式是通过 datadata_end 指针进行的直接内存访问。验证器会追踪这些指针的范围,确保所有访问都在 [data, data_end) 区间内。忘记边界检查是最常见的验证器拒绝原因。

XDP 数据包操作#

XDP 程序不仅可以读取数据包,还可以修改和调整包的大小:

// 修改数据包内容(原地修改)
static __always_inline void swap_mac(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return;
// 交换源 MAC 和目标 MAC(用于 XDP_TX 回包)
__u8 tmp[6];
__builtin_memcpy(tmp, eth->h_source, 6);
__builtin_memcpy(eth->h_source, eth->h_dest, 6);
__builtin_memcpy(eth->h_dest, tmp, 6);
}
// 调整数据包头部空间
// bpf_xdp_adjust_head() 增加或减少包头空间
SEC("xdp")
int xdp_add_header(struct xdp_md *ctx)
{
// 在数据包前面增加 4 字节(用于封装 VLAN 等场景)
if (bpf_xdp_adjust_head(ctx, -4))
return XDP_DROP;
// 调整后需要重新获取 data/data_end 指针
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
// 填充新增的 4 字节...
return XDP_PASS;
}

三、BPF Map 类型体系#

BPF Map 是 eBPF 程序与用户空间(以及其他 BPF 程序)之间共享数据的核心机制。Map 是一个键值存储,BPF 程序通过 helper 函数读写 Map,用户空间通过 bpf() 系统调用读写 Map。

通用 Map 类型#

Map 类型特点典型用途
BPF_MAP_TYPE_ARRAY固定大小数组,key 为索引全局配置、统计计数
BPF_MAP_TYPE_HASH哈希表,key 任意连接跟踪、流表
BPF_MAP_TYPE_LRU_HASH带 LRU 淘汰的哈希表有限容量的连接表
BPF_MAP_TYPE_PERCPU_ARRAY每 CPU 独立数组高频统计(无锁)
BPF_MAP_TYPE_PERCPU_HASH每 CPU 独立哈希表高频更新(无锁)
BPF_MAP_TYPE_QUEUEFIFO 队列任务队列
BPF_MAP_TYPE_STACKLIFO 栈回溯追踪

XDP 专用 Map 类型#

Map 类型特点典型用途
BPF_MAP_TYPE_CPUMAPCPU 重定向映射将包重定向到其他 CPU 处理
BPF_MAP_TYPE_DEVMAP网卡重定向映射将包重定向到其他网卡发送
BPF_MAP_TYPE_XSKMAPAF_XDP 套接字映射将包重定向到用户态 AF_XDP
BPF_MAP_TYPE_DEVMAP_HASH哈希版 DEVMAP按键值选择目标网卡

Map 操作#

BPF 程序通过 helper 函数操作 Map:

// 查找
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key);
// 返回值指针,不存在返回 NULL
// 更新/插入
long bpf_map_update_elem(struct bpf_map *map, const void *key,
const void *value, __u64 flags);
// flags: BPF_ANY (存在则更新,不存在则创建)
// BPF_NOEXIST (仅不存在时创建)
// BPF_EXIST (仅存在时更新)
// 删除
long bpf_map_delete_elem(struct bpf_map *map, const void *key);
// 遍历
long bpf_map_get_next_key(struct bpf_map *map, const void *key,
void *next_key);
// key=NULL 时返回第一个 key

Map 定义与使用示例#

以下是一个完整的 XDP 程序,展示 Map 的定义和使用:

// xdp_lb_kern.c — 基于 XDP 的 L3 负载均衡器
#include <linux/bpf.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// 后端服务器列表
struct backend {
__u32 ip; // 后端 IP(网络字节序)
__u8 mac[6]; // 后端 MAC
};
// 后端服务器 Map(ARRAY 类型,索引即后端编号)
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 16);
__type(key, __u32);
__type(value, struct backend);
} backends SEC(".maps");
// 连接状态表(HASH 类型,五元组 → 后端编号)
struct conn_key {
__u32 src_ip;
__u32 dst_ip;
__u16 src_port;
__u16 dst_port;
__u8 proto;
__u8 pad[3];
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, struct conn_key);
__type(value, __u32); // 后端索引
} conn_table SEC(".maps");
// 统计计数(PERCPU_ARRAY,无锁高效)
enum stats_idx {
STAT_PACKETS,
STAT_REDIRECTED,
STAT_DROPPED,
STAT_MAX
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, STAT_MAX);
__type(key, __u32);
__type(value, __u64);
} stats SEC(".maps");
static __always_inline void stats_inc(__u32 idx)
{
__u64 *val = bpf_map_lookup_elem(&stats, &idx);
if (val)
(*val)++;
}
SEC("xdp")
int xdp_lb(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
stats_inc(STAT_PACKETS);
// 解析以太网头
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS;
// 解析 IP 头
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
// 构造连接键
struct conn_key key = {};
key.src_ip = iph->saddr;
key.dst_ip = iph->daddr;
key.proto = iph->protocol;
// 查找已有连接
__u32 *backend_idx = bpf_map_lookup_elem(&conn_table, &key);
__u32 idx;
if (backend_idx) {
idx = *backend_idx;
} else {
// 新连接:简单哈希选择后端
idx = (iph->saddr + iph->daddr) % 16;
bpf_map_update_elem(&conn_table, &key, &idx, BPF_ANY);
}
// 查找后端信息
struct backend *be = bpf_map_lookup_elem(&backends, &idx);
if (!be) {
stats_inc(STAT_DROPPED);
return XDP_DROP;
}
// 修改目标 MAC 和 IP(DNAT)
__builtin_memcpy(eth->h_dest, be->mac, 6);
iph->daddr = be->ip;
iph->check = 0; // 简化,实际需重算校验和
stats_inc(STAT_REDIRECTED);
return XDP_TX; // 从同一网卡发回
}
char _license[] SEC("license") = "GPL";

[!NOTE] PERCPU_ARRAYPERCPU_HASH 是高频统计的首选。每个 CPU 拥有独立的值副本,BPF 程序更新时无需加锁,避免了多核竞争。用户空间读取时,内核自动聚合所有 CPU 的值。

四、cpumap 与 devmap 重定向#

XDP_REDIRECT 是 XDP 最强大的动作——它允许将数据包重定向到其他 CPU、网卡或用户态套接字,而无需穿越协议栈。三种重定向目标分别通过 CPUMAP、DEVMAP 和 XSKMAP 实现。

CPUMAP:跨 CPU 重定向#

CPUMAP 的设计动机是解决单 CPU 收包瓶颈。当网卡的多队列 RSS(Receive Side Scaling)配置不均,或某些流被哈希到同一 CPU 时,该 CPU 成为热点。CPUMAP 允许 XDP 程序将包重定向到其他 CPU 的输入队列,实现类似 RSS 的负载均衡。

graph LR subgraph CPU 0 A[网卡收包] --> B[XDP 程序] B -->|XDP_REDIRECT| C[CPUMAP] end C -->|enqueue| D[CPU 1 输入队列] C -->|enqueue| E[CPU 2 输入队列] C -->|enqueue| F[CPU 3 输入队列] D --> G[构建 sk_buff<br>进入协议栈] E --> H[构建 sk_buff<br>进入协议栈] F --> I[构建 sk_buff<br>进入协议栈]

CPUMAP 重定向的关键特性:

  • 包在目标 CPU 上构建 sk_buff 并进入协议栈(不是再次执行 XDP 程序)
  • 通过 per-CPU FIFO 队列传递,避免锁竞争
  • 目标 CPU 可以运行 XDP 程序的 cpumap attach 点程序(用于进一步处理)
// cpumap_redirect_kern.c — CPUMAP 重定向示例
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// CPUMAP:key=目标 CPU 编号,value=队列大小
struct {
__uint(type, BPF_MAP_TYPE_CPUMAP);
__uint(max_entries, 64);
__type(key, __u32);
__type(value, __u32);
} cpu_map SEC(".maps");
// 统计
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} redirect_cnt SEC(".maps");
SEC("xdp")
int xdp_cpumap_redirect(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
// 简单策略:根据接收队列选择目标 CPU
// 实际中可根据五元组哈希、协议类型等决定
__u32 dest_cpu = ctx->rx_queue_index % 4;
// 重定向到目标 CPU
long ret = bpf_redirect_map(&cpu_map, dest_cpu, 0);
if (ret == XDP_REDIRECT) {
__u32 key = 0;
__u64 *cnt = bpf_map_lookup_elem(&redirect_cnt, &key);
if (cnt)
(*cnt)++;
}
return ret;
}
// CPUMAP 附着程序:在目标 CPU 上执行
SEC("xdp/cpumap")
int xdp_cpumap_pass(struct xdp_md *ctx)
{
// 在目标 CPU 上可以进一步处理包
// 例如:修改包头、记录日志等
return XDP_PASS; // 最终交给协议栈
}
char _license[] SEC("license") = "GPL";

DEVMAP:跨网卡重定向#

DEVMAP 用于将包从一张网卡重定向到另一张网卡发送,实现简单的二层交换或路由转发:

// devmap_redirect_kern.c — DEVMAP 重定向示例
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// DEVMAP:key=目标网卡 ifindex,value=转发配置
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 64);
__type(key, __u32);
__type(value, __u32); // 目标网卡 ifindex
} dev_map SEC(".maps");
SEC("xdp")
int xdp_devmap_redirect(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
// 根据目标 MAC 查找转发端口
// 简化:重定向到固定网卡
__u32 dest_ifindex = 2; // eth1 的 ifindex
// 修改源 MAC 为当前网卡 MAC(避免 MAC 漂移)
// 实际中需要查路由表获取正确的源 MAC
return bpf_redirect_map(&dev_map, dest_ifindex, 0);
}
char _license[] SEC("license") = "GPL";

XSKMAP:重定向到 AF_XDP 套接字#

XSKMAP 是 AF_XDP 的核心——它将数据包直接重定向到用户态的 AF_XDP 套接字,实现零拷贝收包:

xskmap_redirect_kern.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// XSKMAP:key=队列号,value=AF_XDP 套接字 fd
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 64);
__type(key, __u32);
__type(value, __u32);
} xsk_map SEC(".maps");
SEC("xdp")
int xdp_xsk_redirect(struct xdp_md *ctx)
{
// 根据接收队列号重定向到对应的 AF_XDP 套接字
return bpf_redirect_map(&xsk_map, ctx->rx_queue_index, 0);
}
char _license[] SEC("license") = "GPL";

[!WARNING] bpf_redirect_map() 的第二个参数是 Map 中的 key,不是目标资源的直接标识。例如 CPUMAP 中 key 是 CPU 编号,DEVMAP 中 key 是网卡索引。如果 key 不存在于 Map 中,包会被静默丢弃(返回 XDP_DROP)。

五、AF_XDP:零拷贝套接字#

AF_XDP(Address Family XDP)是 XDP 生态的用户态接口。它允许用户态程序通过套接字直接从网卡接收和发送数据包,无需穿越内核协议栈。AF_XDP 是 XDP 与 DPDK 之间的桥梁——它提供了类似 DPDK 的零拷贝能力,但不需要独占 CPU 核心或使用大页内存。

AF_XDP 架构#

AF_XDP 套接字的核心设计围绕**四个环形缓冲区(ring)**展开:

graph TB subgraph 内核空间 A[网卡驱动] -->|DMA| B[rx_ring] B --> C[XDP 程序] C -->|XSKMAP redirect| D[AF_XDP 内核侧] end subgraph 用户空间 E[UMEM<br>用户态共享内存区域] F[Fill Ring<br>用户→内核:提供空缓冲区] G[Completion Ring<br>内核→用户:回收已发送缓冲区] H[RX Ring<br>内核→用户:已接收数据包] I[TX Ring<br>用户→内核:待发送数据包] E -.->|提供 addr/desc| F F -.->|传递 desc| D D -.->|填充 desc| H H -.->|读取数据| E E -.->|提供 addr/desc| I I -.->|传递 desc| D D -.->|发送完成| G G -.->|回收 desc| E end style E fill:#e1bee7,stroke:#6a1b9a style F fill:#bbdefb,stroke:#1565c0 style G fill:#c8e6c9,stroke:#2e7d32 style H fill:#ffcc80,stroke:#e65100 style I fill:#ef9a9a,stroke:#c62828

UMEM:用户态共享内存#

UMEM 是 AF_XDP 的基础——它是一块在用户态分配的连续内存区域,被划分为固定大小的帧(frame),内核和用户态共享访问:

// UMEM 配置
struct xdp_umem_reg {
__u64 addr; // UMEM 起始地址
__u64 len; // UMEM 总长度
__u32 chunk_size; // 帧大小(通常 4096)
__u32 headroom; // 每帧预留的头部空间
__u32 flags; // 标志位
};

UMEM 的工作流程:

  1. 用户态分配内存:通过 mmap()posix_memalign() 分配大块连续内存
  2. 注册到内核:通过 setsockopt(sock, SOL_XDP, XDP_UMEM_REG, ...) 注册
  3. 帧描述符传递:通过 Fill/Completion/RX/TX 四个 ring 传递帧的地址描述符(addr + len),而非数据本身
  4. 零拷贝:内核直接在 UMEM 帧中写入/读取数据,无需 copy_to_user / copy_from_user

四个 Ring 详解#

Ring方向用途生产者消费者
Fill Ring用户→内核告诉内核哪些 UMEM 帧可用于接收用户态内核
Completion Ring内核→用户通知用户态哪些帧已发送完毕可回收内核用户态
RX Ring内核→用户传递已接收的数据包描述符内核用户态
TX Ring用户→内核传递待发送的数据包描述符用户态内核

数据接收流程:

1. 用户态在 Fill Ring 中放入空帧描述符(addr=帧偏移, len=帧大小)
2. 网卡收到数据包 → XDP 程序 → XSKMAP redirect
3. 内核从 Fill Ring 取出空帧描述符
4. 内核将数据包 DMA 写入 UMEM 对应帧
5. 内核在 RX Ring 中放入已填充帧的描述符
6. 用户态从 RX Ring 读取描述符,直接访问 UMEM 中的数据

数据发送流程:

1. 用户态在 UMEM 帧中构造数据包
2. 用户态在 TX Ring 中放入帧描述符
3. 内核从 TX Ring 取出描述符
4. 内核触发网卡 DMA 发送 UMEM 帧中的数据
5. 内核在 Completion Ring 中放入已发送帧的描述符
6. 用户态从 Completion Ring 回收帧,放回 Fill Ring 复用

零拷贝模式 vs 拷贝模式#

AF_XDP 有两种数据传输模式:

特性零拷贝模式拷贝模式
数据传输内核直接在 UMEM 帧中读写内核拷贝数据到/从 UMEM 帧
性能更高(无内存拷贝)较低(有一次内存拷贝)
硬件要求需要网卡驱动支持零拷贝所有网卡都支持
配置XDP_ZEROCOPY 标志XDP_COPY 标志
典型延迟1-2μs3-5μs
# 查看网卡是否支持 AF_XDP 零拷贝
ethtool -i eth0
# 需要驱动支持 XDP_ZEROCOPY(如 i40e, ice, mlx5, virtio)

[!NOTE] 零拷贝模式要求网卡驱动支持将 DMA 缓冲区直接映射到用户态 UMEM。目前支持零拷贝的驱动包括:i40e(Intel XL710)、ice(Intel E810)、mlx5(Mellanox ConnectX)、virtio(虚拟化场景)。如果驱动不支持零拷贝,AF_XDP 会自动回退到拷贝模式。

AF_XDP 收包示例#

以下是一个简化的 AF_XDP 收包程序,展示核心数据路径:

// af_xdp_rx.c — AF_XDP 收包示例
#include <linux/if_xdp.h>
#include <bpf/xsk.h>
#include <bpf/libbpf.h>
#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define NUM_FRAMES 4096
#define FRAME_SIZE XSK_UMEM_DEFAULT_FRAME_SIZE // 4096
#define RX_BATCH_SIZE 64
// UMEM 和 XSK 配置
struct xsk_socket_info {
struct xsk_ring_cons rx; // RX Ring 消费者
struct xsk_ring_prod fill; // Fill Ring 生产者
struct xsk_ring_cons comp; // Completion Ring 消费者
struct xsk_ring_prod tx; // TX Ring 生产者
struct xsk_socket *xsk;
struct xsk_umem *umem;
void *buffer; // UMEM 缓冲区
};
static struct xsk_socket_info *
xsk_configure_socket(const char *ifname, __u32 queue_id)
{
struct xsk_socket_info *xsk_info;
struct xsk_umem_config umem_cfg = {
.fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.frame_size = FRAME_SIZE,
.frame_headroom = XSK_UMEM_DEFAULT_FRAME_HEADROOM,
.flags = 0, // 0=自动选择(优先零拷贝)
};
struct xsk_socket_config xsk_cfg = {
.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.libbpf_flags = 0,
.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST,
.bind_flags = XDP_ZEROCOPY, // 优先使用零拷贝
};
xsk_info = calloc(1, sizeof(*xsk_info));
// 分配 UMEM 缓冲区
xsk_info->buffer = aligned_alloc(getpagesize(),
NUM_FRAMES * FRAME_SIZE);
// 创建 UMEM
xsk_umem__create(&xsk_info->umem, xsk_info->buffer,
NUM_FRAMES * FRAME_SIZE,
&xsk_info->fill, &xsk_info->comp,
&umem_cfg);
// 创建 XSK 套接字
xsk_socket__create(&xsk_info->xsk, ifname, queue_id,
xsk_info->umem,
&xsk_info->rx, &xsk_info->tx,
&xsk_cfg);
// 预填充 Fill Ring(提供空帧给内核)
__u32 idx;
__u32 n = xsk_ring_prod__reserve(&xsk_info->fill,
NUM_FRAMES, &idx);
for (__u32 i = 0; i < n; i++) {
*xsk_ring_prod__fill_addr(&xsk_info->fill, idx + i) =
i * FRAME_SIZE;
}
xsk_ring_prod__submit(&xsk_info->fill, n);
return xsk_info;
}
// 收包主循环
static void rx_and_process(struct xsk_socket_info *xsk_info)
{
for (;;) {
__u32 idx_rx = 0;
__u32 rcvd = xsk_ring_cons__peek(&xsk_info->rx,
RX_BATCH_SIZE,
&idx_rx);
if (!rcvd) {
// 无数据,poll 等待
struct pollfd fds = {
.fd = xsk_socket__fd(xsk_info->xsk),
.events = POLLIN,
};
poll(&fds, 1, -1);
continue;
}
// 批量处理已接收的数据包
for (__u32 i = 0; i < rcvd; i++) {
__u64 addr = xsk_ring_cons__rx_desc(&xsk_info->rx,
idx_rx + i)->addr;
__u32 len = xsk_ring_cons__rx_desc(&xsk_info->rx,
idx_rx + i)->len;
// 直接访问 UMEM 中的数据包(零拷贝!)
__u8 *pkt = xsk_umem__get_data(xsk_info->buffer, addr);
// 处理数据包...
printf("收到 %u 字节数据包,首字节: 0x%02x\n",
len, pkt[0]);
// 处理完毕,将帧放回 Fill Ring 复用
__u32 idx_fill;
xsk_ring_prod__reserve(&xsk_info->fill, 1, &idx_fill);
*xsk_ring_prod__fill_addr(&xsk_info->fill, idx_fill) = addr;
xsk_ring_prod__submit(&xsk_info->fill, 1);
}
// 释放已处理的 RX 描述符
xsk_ring_cons__release(&xsk_info->rx, rcvd);
}
}
int main(int argc, char **argv)
{
const char *ifname = argc > 1 ? argv[1] : "eth0";
__u32 queue_id = argc > 2 ? atoi(argv[2]) : 0;
struct xsk_socket_info *xsk_info =
xsk_configure_socket(ifname, queue_id);
printf("AF_XDP 套接字已绑定到 %s 队列 %u\n", ifname, queue_id);
rx_and_process(xsk_info);
return 0;
}

[!WARNING] AF_XDP 程序需要配合 XDP 程序使用——XDP 程序通过 XSKMAP 将包重定向到 AF_XDP 套接字。如果 XDP 程序没有将包 redirect 到 XSKMAP,AF_XDP 套接字不会收到任何数据。libxdp 库会自动处理 XDP 程序的加载和 XSKMAP 的配置。

libxdp 与 xsk_socket 配置#

libxdp 是 AF_XDP 开发的推荐库,它封装了 XDP 程序加载、XSKMAP 管理、多程序复用等复杂逻辑:

# 安装 libxdp 开发包
sudo apt install libxdp-dev
# 或从源码编译
git clone https://github.com/xdp-project/xdp-tools.git
cd xdp-tools
make lib/libxdp
sudo make install

libxdp 的核心优势:

  • XDP 程序复用:多个 XDP 程序可以链式挂载到同一网卡(通过 dispatcher 机制)
  • 自动 XSKMAP 管理:创建 AF_XDP 套接字时自动配置 XSKMAP
  • 回退机制:零拷贝不可用时自动回退到拷贝模式
  • AF_XDP 多队列支持:每个队列一个 XSK 套接字

六、XDP 与 DPDK 全面对比#

XDP 和 DPDK 是高性能网络领域的两大主流方案。它们的目标相似——最大化网络包处理性能——但技术路线截然不同。以下从多个维度进行全面对比。

对比总览#

维度XDP / eBPFDPDK
旁通级别部分旁通(跳过 sk_buff 和协议栈,仍在内核中)完全旁通(用户态轮询,内核不参与数据面)
峰值性能单核 10-20 Mpps(原生模式)单核 40-80 Mpps(轮询模式)
CPU 占用按需处理,空闲时 CPU 可用于其他任务轮询模式,100% 占用指定核心
内存模型使用内核内存,无需大页需要 HugePages(2MB/1GB),UIO/VFIO
编程模型BPF C(受限子集),验证器保证安全完整 C/C++,无限制
调试难度验证器错误信息晦涩,JIT 后难以调试常规用户态调试(GDB、valgrind)
生态系统内核原生,无需额外库;bpftool、libxdp丰富的 PMD 驱动;dpdk-devbind、testpmd
部署复杂度低:内核 4.18+ 原生支持,无需特殊硬件高:需要 VFIO/UIO、大页配置、网卡绑定
热升级原子替换 XDP 程序,无丢包需要重启应用
协议栈共存XDP_PASS 流量正常走协议栈完全旁通,协议栈不可用
适用场景DDoS 防护、负载均衡、防火墙、可观测性NFV、虚拟交换机、深度包检测、高频交易
网卡要求驱动支持 XDP 原生模式即可需要支持 VFIO/UIO 的网卡

性能深度分析#

XDP 的性能特征

  • XDP_DROP 路径:约 24-30 个时钟周期(原生模式),单核可达 20+ Mpps
  • XDP_TX 路径:约 50-80 个时钟周期,需修改 MAC/IP 头
  • XDP_REDIRECT 路径:约 40-60 个时钟周期,取决于目标类型
  • XDP_PASS 路径:与正常协议栈收包相当(XDP 开销可忽略)

DPDK 的性能特征

  • 纯用户态轮询,无中断、无系统调用、无上下文切换
  • 单核可达 80+ Mpps(小包,现代网卡)
  • 延迟极低:1-3μs 端到端
  • 代价:轮询核心 100% CPU 占用

何时选择 XDP#

  • 需要与内核协议栈共存:XDP_PASS 允许正常流量走协议栈,DPDK 则完全旁通
  • DDoS 防护:在驱动层丢弃攻击流量,保护协议栈不被压垮
  • 负载均衡前置:XDP 做四层负载均衡,后端仍用内核协议栈
  • 可观测性:XDP 程序可以统计和采样网络流量,对正常流量无影响
  • 运维简便:不需要大页、VFIO、独占 CPU 等特殊配置

何时选择 DPDK#

  • 需要极致性能:NFV、虚拟交换机(OVS-DPDK)、5G UPF
  • 深度包处理:需要维护复杂的有状态连接表、深度解析协议
  • 用户态生态:已有大量 DPDK 代码和库(如 SPDK、VPP)
  • 确定性延迟:轮询模式提供可预测的延迟

混合方案:XDP + DPDK#

在实际部署中,XDP 和 DPDK 并非互斥。一种常见的混合架构是:

┌─────────────────────────┐
│ XDP 前置过滤 │
│ DDoS 防护 / 早期丢弃 │
└────────┬────────────────┘
┌───────────┴───────────┐
│ │
XDP_DROP(攻击流量) XDP_REDIRECT
┌────────┴────────┐
│ │
XDP_PASS AF_XDP
(协议栈处理) (用户态高速处理)
│ │
内核 TCP/IP DPDK 应用
正常业务流量 深度包检测/转发

这种架构的优势:

  1. XDP 前置过滤:在驱动层丢弃 DDoS 流量,保护后端
  2. AF_XDP 高速路径:需要深度处理的流量通过 AF_XDP 送到用户态
  3. 协议栈回退:普通 TCP 流量走内核协议栈,复用内核的 TCP 实现

[!NOTE] Facebook(Meta)的 Katran 负载均衡器就是基于 XDP 的典型生产案例。它在生产环境中处理数 Tbps 的流量,证明了 XDP 在大规模部署中的可靠性。Cloudflare 也使用 XDP 进行 DDoS 防护。

七、动手实践#

实践 1:编译和加载一个简单的 XDP 程序#

# 1. 安装依赖
sudo apt install clang llvm gcc-multilib libbpf-dev linux-headers-$(uname -r)
# 2. 编写 XDP 程序(使用上面的 xdp_stats_func 示例)
cat > xdp_stats.c << 'EOF'
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 5);
__type(key, __u32);
__type(value, __u64);
} xdp_stats_map SEC(".maps");
SEC("xdp")
int xdp_stats_func(struct xdp_md *ctx)
{
__u32 key = XDP_PASS;
__u64 *count = bpf_map_lookup_elem(&xdp_stats_map, &key);
if (count)
(*count)++;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
EOF
# 3. 编译为 BPF 字节码
clang -O2 -target bpf -c xdp_stats.c -o xdp_stats.o
# 4. 加载 XDP 程序到网卡(通用模式,无需驱动支持)
sudo ip link set dev eth0 xdpgeneric off # 先卸载已有程序
sudo ip link set dev eth0 xdpgeneric obj xdp_stats.o sec xdp
# 5. 验证程序已加载
ip link show dev eth0 | grep xdp
# 输出类似:xdpgeneric/id 42
# 6. 卸载 XDP 程序
sudo ip link set dev eth0 xdpgeneric off

如果网卡驱动支持原生 XDP(如 i40e, ice, mlx5),使用原生模式获得最佳性能:

# 原生模式加载
sudo ip link set dev eth0 xdp obj xdp_stats.o sec xdp
# 或使用 libxdp 的 xdp-loader 工具(支持多程序链式挂载)
xdp-loader load eth0 xdp_stats.o
# 查看已加载的 XDP 程序
xdp-loader status
# 卸载
xdp-loader unload eth0 --all

实践 2:使用 bpftool 检查 Map 和程序#

bpftool 是 eBPF 的瑞士军刀,可以检查程序、Map、以及运行时状态:

# 列出所有已加载的 BPF 程序
sudo bpftool prog list
# 输出示例:
# 42: xdp name xdp_stats_func tag 57cd311f2c27e4cf gpl
# loaded_at 2026-04-21T10:00:00+0000 uid 0
# xlated 48B jited 57B memlock 4096B map_ids 43
# 查看特定程序的详细信息
sudo bpftool prog show id 42
# 查看程序的 BPF 指令(反汇编)
sudo bpftool prog dump xlated id 42
# 查看 JIT 编译后的原生指令
sudo bpftool prog dump jited id 42
# 列出所有 BPF Map
sudo bpftool map list
# 输出示例:
# 43: percpu_array name xdp_stats_map flags 0x0
# key 4B value 8B max_entries 5 memlock 4096B
# 查看 Map 内容
sudo bpftool map dump id 43
# 输出每个 key 对应的值(percpu map 会显示每个 CPU 的值)
# 查询特定 key
sudo bpftool map lookup id 43 key 0 0 0 0
# key = 0 (XDP_PASS) 的计数值
# 实时跟踪 Map 变化
sudo bpftool map event id 43

实践 3:构建 AF_XDP 套接字程序#

# 1. 安装 libxdp 和 libbpf
sudo apt install libxdp-dev libbpf-dev
# 2. 编译 AF_XDP 收包示例(使用上面的 af_xdp_rx.c)
gcc -O2 -o af_xdp_rx af_xdp_rx.c -lxdp -lbpf
# 3. 编译配套的 XDP 程序(XSKMAP redirect)
clang -O2 -target bpf -c xskmap_redirect.c -o xskmap_redirect.o
# 4. 运行 AF_XDP 程序
sudo ./af_xdp_rx eth0 0
# 输出:AF_XDP 套接字已绑定到 eth0 队列 0
# 收到 64 字节数据包,首字节: 0x00
# 5. 使用 xdp-bench 工具测试 AF_XDP 性能
# xdp-bench 是 xdp-tools 的一部分
git clone https://github.com/xdp-project/xdp-tools.git
cd xdp-tools && make
# 运行 AF_XDP 吞吐量测试
sudo ./xdp-bench rx eth0 -a # 所有队列
sudo ./xdp-bench rx eth0 -q 0 # 指定队列
# 6. 使用 testpmd(DPDK 自带)对比 DPDK 性能
# 需要 VFIO 和大页配置,参考第 2 章的 DPDK 实践

实践 4:基准测试 XDP_DROP vs 内核丢包#

这个实验直观展示 XDP 在驱动层丢包与内核协议栈丢包的性能差距:

# 1. 编写 XDP_DROP 程序
cat > xdp_drop_all.c << 'EOF'
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int xdp_drop_all(struct xdp_md *ctx)
{
return XDP_DROP; // 丢弃所有包
}
char _license[] SEC("license") = "GPL";
EOF
clang -O2 -target bpf -c xdp_drop_all.c -o xdp_drop_all.o
# 2. 加载 XDP_DROP 程序
sudo ip link set dev eth0 xdpgeneric obj xdp_drop_all.o sec xdp
# 3. 使用 pktgen 或外部流量发生器发送高速流量
# 方法 A:使用内核 pktgen 模块
sudo modprobe pktgen
echo "add_device eth0" | sudo tee /proc/net/pktgen/kpktgend_0
echo "pkt_size 64" | sudo tee /proc/net/pktgen/eth0
echo "count 0" | sudo tee /proc/net/pktgen/eth0
echo "start" | sudo tee /proc/net/pktgen/pgctrl
# 方法 B:使用 MoonGen/TrafficGen 等外部工具
# 4. 观察 XDP 丢包统计
sudo bpftool prog show # 查看 XDP 程序
cat /proc/net/dev # 查看网卡统计(rx packets vs dropped)
ethtool -S eth0 # 查看网卡详细统计
# 5. 对比:卸载 XDP,使用 iptables/nftables 丢包
sudo ip link set dev eth0 xdpgeneric off
sudo iptables -A INPUT -j DROP
# 6. 再次发送流量,对比性能
# XDP_DROP 通常比 iptables DROP 快 5-10 倍
# 7. 清理
sudo iptables -D INPUT -j DROP

预期结果对比:

丢包方式处理位置单核吞吐量CPU 占用
XDP_DROP(原生模式)驱动层,sk_buff 之前20+ Mpps
XDP_DROP(通用模式)netif_receive_skb 之后5-10 Mpps
nftables DROPNetfilter INPUT 钩子2-5 Mpps
iptables DROPNetfilter INPUT 钩子1-3 Mpps

[!WARNING] XDP_DROP 会丢弃所有匹配的包,包括 SSH 等管理流量。在生产环境中务必添加白名单规则,确保管理通道不会被意外阻断。建议在 XDP 程序中优先放行 SSH(端口 22)和管理协议。

小结#

本章深入剖析了 XDP 与 eBPF 这对高性能网络的核心技术组合:

  1. eBPF 架构:BPF 指令集(10 寄存器、64 位)→ 验证器(DFS 遍历 DAG,保证安全)→ JIT 编译(原生机器码)→ 内核 Hook 点执行。eBPF 的安全模型以验证器为核心,在加载时保证程序不会崩溃内核

  2. XDP 快速路径:Hook 点在 DMA 之后、sk_buff 之前——这是性能优势的根源。五种动作(PASS/DROP/REDIRECT/TX/ABORTED)覆盖了包处理的所有需求

  3. BPF Map 类型体系:从通用的 ARRAY/HASH 到 XDP 专用的 CPUMAP/DEVMAP/XSKMAP,Map 是 BPF 程序与用户空间、BPF 程序之间的数据共享桥梁

  4. 重定向机制:CPUMAP 实现 CPU 间负载均衡,DEVMAP 实现跨网卡转发,XSKMAP 连接 XDP 与 AF_XDP 用户态处理

  5. AF_XDP 零拷贝套接字:UMEM 共享内存 + 四个 Ring(Fill/Completion/RX/TX)实现内核与用户态的零拷贝数据传递,是 XDP 生态的用户态接口

  6. XDP vs DPDK:XDP 是内核态的快速路径(部分旁通、按需处理、与协议栈共存),DPDK 是用户态的完全旁通(极致性能、独占 CPU、放弃内核生态)。两者并非互斥,混合架构可以兼得优势

XDP 代表了 Linux 内核网络处理的范式转变——从”所有包都走协议栈”到”在最早时机做出决策”。它不需要像 DPDK 那样完全脱离内核,而是在内核内部开辟了一条可编程的快速路径,让开发者能够在保持内核生态的同时获得接近旁通方案的性能。

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

部分信息可能已经过时

相关文章 智能推荐
1
综合实战:构建高性能网络应用
高性能网络 综合实战——技术选型决策树、构建 DPDK L4 负载均衡器(全代码走读)、集成 XDP DDoS 防护、RDMA 远程存储访问、性能基准方法论(pktgen/TRex/MoonGen)、调优检查清单——将前 14 章知识融会贯通,构建生产级高性能网络应用。
2
内核旁通技术全景
高性能网络 全景式对比五大内核旁通技术路线——DPDK(用户态轮询)、netmap(内存映射环)、PF_RING(内核模块环缓冲)、XDP/eBPF(内核快速路径)、io_uring(异步 I/O)——从架构哲学、性能特征、适用场景到选型决策树,帮你找到最适合的高性能网络方案。
3
高性能网络与系统底层技术
高性能网络 本系列从内核网络栈的性能瓶颈出发——系统剖析 DPDK、XDP/eBPF、RDMA、SPDK、io_uring 等内核旁通与高性能技术,从「为什么内核网络慢」到「如何绕过内核实现极限性能」,每章配有可编译运行的代码示例与性能基准,让你从「会用网络 API」进阶到「掌控数据平面性能」。
4
OVS-DPDK 与虚拟交换
高性能网络 深入 OVS-DPDK 与虚拟交换——OVS-DPDK 架构与内核 OVS 对比、dpdkvhostuser 端口类型、vhost-user 协议与共享 virtio 环、virtio 前后端、VM-to-VM 交换路径、流分类(EMC/DPCLS/megaflows)、性能调优——掌握云环境虚拟网络加速的完整技术栈。
5
SmartNIC 与 DPU
高性能网络 深入 SmartNIC 与 DPU——硬件卸载概念与收益、SmartNIC 架构(固定功能 vs 可编程)、DPU 产品矩阵(NVIDIA BlueField/AMD Pensando/Intel IPU)、OVS 硬件卸载(tc/rte_flow/ASAP²)、P4 编程入门——掌握硬件加速网络的完整技术栈。