2. Linux网络数据包收发路径剖析
要理解 AF_XDP 为什么能绕过协议栈、又为什么必须与内核协作,必须先看清一个数据包在 Linux 中“从网线到应用”的完整旅程。本节沿着接收方向(RX)为主线,逐层拆解网卡驱动、NAPI、DMA 环形缓冲区、SKB 分配、协议栈处理这几个关键阶段,并在每个阶段标注 AF_XDP 的介入点与性能瓶颈所在。发送方向(TX)是对称的逆过程,理解 RX 后 TX 可类推。
一、硬件与驱动的第一站:DMA 与 RX 环形缓冲区
网卡收到以太网帧后,不会直接打断 CPU。现代网卡通过 DMA 把数据写入内核预先分配的内存缓冲区,同时把“描述符”(descriptor)写入 RX Ring。描述符本身很小,通常只包含缓冲区物理地址、长度、状态标志,真正的数据体在独立的内存页中。这个设计的意义在于:CPU 不需要搬运数据,只需读取描述符即可知道“哪个缓冲区有包、包多长”。
RX Ring 是一个环形队列,由驱动在初始化时分配,典型大小 256~4096 个描述符。网卡通过头指针(head)写入新描述符,驱动通过尾指针(tail)回收已处理描述符。当网卡写入的描述符数量达到预设阈值(如 64 个),或定时器到期,网卡触发中断。这就是“中断合并”(interrupt coalescing)的由来——它用延迟换取更少的中断次数。
二、NAPI:中断与轮询的混合机制
如果每个包都触发一次中断,在 10Gbps 线速下每秒会有上百万次中断,CPU 会完全陷入中断风暴。NAPI(New API)的核心思想是:第一个包到来时用中断唤醒,随后关闭该网卡队列的中断,改为软中断中的轮询(poll),直到 RX Ring 被清空或达到预算(budget,默认 64)才重新开启中断。
软中断 NET_RX_SOFTIRQ 由 ksoftirqd 内核线程或中断退出路径执行。驱动注册的 poll 函数(如 ixgbe_poll、i40e_napi_poll)被调用后,会循环从 RX Ring 取描述符,为每个包分配 SKB,填充元数据,然后交给上层。这里的预算机制非常关键:单次 poll 最多处理 budget 个包,处理完若还有剩余,会重新调度软中断,避免单个 CPU 长时间独占。
三、SKB:协议栈的通用载体
SKB(struct sk_buff)是 Linux 网络栈的核心数据结构,它不存储数据本身,而是通过指针指向数据缓冲区,并携带协议栈需要的全部元数据:协议类型、入接口、优先级、校验和状态、分片信息、路由缓存等。分配一个 SKB 需要从 slab 缓存取内存,初始化几十个字段,这个开销在高速率下非常可观。
SKB 分配完成后,驱动会设置 skb->dev、skb->protocol,调用 napi_gro_receive 进入 GRO(Generic Receive Offload)阶段。GRO 把同一流中连续的多个小包合并成一个大包,减少上层协议栈的处理次数。随后数据包进入 __netif_receive_skb_core,根据协议类型分发给 IP 层、ARP 层或直接交给抓包点(如 tcpdump 的 ptype_all)。
四、协议栈处理:IP、TCP/UDP 与 Socket 队列
进入 IP 层后,内核做路由查找、分片重组、校验和验证;进入 TCP/UDP 层后,做端口查找、连接状态匹配、序列号处理、拥塞控制、接收窗口更新。最终数据被放入对应 socket 的接收队列(sk_receive_queue),唤醒等待的应用进程,由 recvmsg 系统调用拷贝到用户态缓冲区。
这条路径上有几个显著瓶颈:第一,SKB 的分配与释放带来内存管理开销;第二,协议栈逐层处理带来大量分支判断和缓存不友好访问;第三,数据从内核缓冲区拷贝到用户态,10Gbps 下这个拷贝本身就要消耗可观的 CPU 和内存带宽;第四,系统调用与上下文切换在高包速率下成为固定成本。
五、AF_XDP 的介入逻辑与收益边界
从图中可以看到,AF_XDP 的 XDP 程序挂载在 NAPI poll 的早期,此时数据还在 DMA 缓冲区中,SKB 尚未分配。XDP 程序返回 XDP_REDIRECT 时,驱动直接把描述符重定向到 AF_XDP socket 关联的 UMEM(用户态内存)中,用户态程序通过 mmap 映射的 RX Ring 和 FILL Ring 直接读取数据。整个过程没有 SKB 分配、没有协议栈遍历、没有内核到用户态的拷贝。
但 AF_XDP 并非万能。它要求网卡驱动支持 XDP 原生模式(native mode),且 UMEM 需要预先注册并锁定物理页。对于需要完整 TCP 协议栈语义的应用,AF_XDP 只提供原始帧,TCP 重传、拥塞控制、连接管理都需要用户态自行实现或借助 DPDK 等库。因此它最适合的场景是:自定义协议、高频交易、DNS 服务器、负载均衡器、视频流处理等对延迟和吞吐极度敏感、且能接受自行处理协议逻辑的场合。
六、发送路径的对称性
TX 方向与 RX 对称:应用调用 sendmsg 后,数据进入 socket 发送队列,经 TCP/UDP 层封装、IP 层路由,最终由驱动从 TX Ring 取描述符,DMA 读取数据发送到网线。AF_XDP 在 TX 方向同样提供零拷贝路径:用户态把数据帧写入 UMEM 的 TX 区域,通过 TX Ring 通知内核,驱动直接从 UMEM DMA 发送,跳过 SKB 和协议栈。理解 RX 路径后,TX 只需把“描述符回收”换成“描述符提交”,把“FILL Ring”换成“COMPLETION Ring”即可。