2. Linux 内核协议栈基础:网络分层模型、套接字缓冲区(skb)、协议栈数据流路径

在深入 DPDK KNI 之前,必须理解 Linux 内核协议栈的核心机制。本章将剖析网络分层模型、套接字缓冲区(sk_buff)结构以及数据包在内核中的完整流经路径。这些知识是理解 KNI 如何与内核交互、以及性能瓶颈所在的基础。

2.1 网络分层模型:内核视角的 TCP/IP 协议栈

Linux 内核协议栈严格遵循 TCP/IP 分层模型,但实现上进行了优化与合并。从数据包处理的角度,主要分为以下三层:

层次 内核模块/子系统 核心数据结构 主要功能
L2 链路层 网络设备子系统 (net_device) sk_buff、net_device 驱动接口、MAC 地址解析、ARP、桥接、VLAN
L3 网络层 IPv4/IPv6 协议栈 sk_buff、dst_entry 路由查找、IP 分片与重组、Netfilter 钩子
L4 传输层 TCP/UDP 协议模块 sk_buff、sock、tcp_sock 连接管理、流量控制、校验和计算、数据分段
关键认知: 内核协议栈的每一层都通过 sk_buff 传递数据,并利用 net_device 和 sock 结构体维护状态。理解这些结构的布局是优化数据路径的前提。

下图展示了数据包从网卡驱动到用户态套接字的完整分层路径:

Linux 内核协议栈分层模型与数据流 Linux 内核协议栈分层模型与数据流 用户态应用 (Socket API) 系统调用接口 (sys_socket / sys_sendmsg / sys_recvmsg) 传输层 (L4) TCP / UDP 协议处理 (tcp_v4_rcv / udp_rcv) 网络层 (L3) IP 路由 / Netfilter 钩子 / 分片重组 (ip_rcv / ip_forward) 链路层 (L2) 网络设备子系统 / 驱动层 (netif_receive_skb / dev_queue_xmit) 物理网卡硬件 (NIC / DMA) 接收 (RX) 发送 (TX) 每层通过 sk_buff 传递,并调用对应钩子函数

2.2 套接字缓冲区 (sk_buff):协议栈的核心数据单元

sk_buff 是 Linux 内核网络子系统中最关键的数据结构。它贯穿整个协议栈,承载着数据包的所有元数据和控制信息。理解其布局是进行性能优化的第一步。

2.2.1 sk_buff 的内存布局

每个 sk_buff 实例管理一个数据缓冲区,该缓冲区包含头部空间、数据区和尾部空间。指针操作是零拷贝的关键:

struct sk_buff {
    /* 链表管理 */
    struct sk_buff      *next;
    struct sk_buff      *prev;
    struct sk_buff_head *list;

    /* 协议层指针 */
    union {
        struct tcphdr   *th;    /* 传输层头部 */
        struct udphdr   *uh;
        struct icmphdr  *icmph;
    };
    struct iphdr        *ip_hdr;    /* 网络层头部 */
    struct ethhdr       *mac_header; /* 链路层头部 */

    /* 数据指针 */
    unsigned char       *head;      /* 缓冲区起始 */
    unsigned char       *data;      /* 数据起始 */
    unsigned char       *tail;      /* 数据结束 */
    unsigned char       *end;       /* 缓冲区结束 */

    /* 长度信息 */
    unsigned int        len;        /* 实际数据长度 */
    unsigned int        data_len;   /* 分片数据长度 */
    unsigned int        truesize;   /* 总分配大小 */

    /* 设备与路由 */
    struct net_device   *dev;       /* 关联网络设备 */
    struct dst_entry    *dst;       /* 路由缓存 */

    /* 时间戳 */
    ktime_t             tstamp;     /* 接收时间戳 */
};
性能要点: sk_buff 的 head、data、tail、end 四个指针构成了一个滑动窗口。协议栈在向下传递时,通过 skb_push() 在数据前添加头部;向上传递时,通过 skb_pull() 剥离头部。这种设计避免了数据拷贝,但频繁的指针操作会消耗 CPU 缓存。

2.2.2 sk_buff 的指针操作与数据流

下图展示了数据包在协议栈各层处理时,sk_buff 指针的变化过程:

sk_buff 指针操作与协议层封装/解封装 sk_buff 指针操作与协议层封装/解封装 线性数据缓冲区 (head ~ end) headroom 数据区 (data ~ tail) tailroom head data tail end 发送路径:逐层封装 (skb_push) L2 头 L3 头 L4 头 应用数据 (Payload) 接收路径:逐层解封装 (skb_pull) L2 头 L3 头 L4 头 应用数据 (Payload) skb_push skb_pull skb_push 减少 data 指针;skb_pull 增加 data 指针,均不拷贝数据

2.3 协议栈数据流路径:从网卡到套接字

数据包从物理网卡到达用户态应用,经历了一条精心设计的函数调用链。理解这条路径上的每个关键节点,是定位性能瓶颈的基础。

2.3.1 接收路径 (RX) 关键函数链

步骤 函数/模块 位置 关键操作
1 ixgbe_clean_rx_irq() 驱动层 从 DMA ring 获取数据,分配 sk_buff
2 napi_gro_receive() NAPI 层 GRO 合并,减少上层处理次数
3 netif_receive_skb() 设备子系统 分发到协议层,触发 Netfilter 钩子
4 ip_rcv() 网络层 IP 校验、路由查找、Netfilter NF_INET_PRE_ROUTING
5 ip_local_deliver() 网络层 分片重组,Netfilter NF_INET_LOCAL_IN
6 tcp_v4_rcv() / udp_rcv() 传输层 查找套接字,处理 TCP 状态机
7 tcp_v4_do_rcv() 传输层 将数据放入接收队列,唤醒用户进程
性能陷阱: 接收路径上的 Netfilter 钩子(如 iptables 规则)和路由缓存查找是主要的 CPU 消耗点。在高速率场景下,这些操作会导致严重的缓存缺失和锁竞争。

2.3.2 发送路径 (TX) 关键函数链

步骤 函数/模块 位置 关键操作
1 tcp_sendmsg() / udp_sendmsg() 传输层 从用户态拷贝数据,构建 sk_buff
2 ip_local_out() 网络层 Netfilter NF_INET_LOCAL_OUT
3 ip_output() 网络层 路由查找、分片、Netfilter NF_INET_POST_ROUTING
4 dev_queue_xmit() 设备子系统 排队规则 (Qdisc) 处理,触发 Netfilter NF_INET_POST_ROUTING
5 sch_direct_xmit() Qdisc 层 调用驱动发送函数
6 ixgbe_xmit_frame() 驱动层 将 sk_buff 映射到 DMA ring,触发发送

2.3.3 完整数据流路径图

下图整合了接收与发送路径,展示了数据包在内核协议栈中的完整生命周期:

Linux 内核协议栈完整数据流路径 Linux 内核协议栈完整数据流路径 接收路径 (RX) ① 网卡 DMA → Ring Buffer ② 驱动中断/NAPI (ixgbe_clean_rx_irq) ③ GRO 合并 (napi_gro_receive) ④ 设备分发 (netif_receive_skb) ⑤ IP 层处理 (ip_rcv / ip_local_deliver) ⑥ TCP/UDP 层 (tcp_v4_rcv / udp_rcv) ⑦ Socket 接收队列 → 用户态 发送路径 (TX) ① 用户态 → Socket 发送队列 ② TCP/UDP 层 (tcp_sendmsg / udp_sendmsg) ③ IP 层 (ip_local_out / ip_output) ④ 邻居子系统 (arp / ndisc) ⑤ Qdisc 排队 (dev_queue_xmit) ⑥ 驱动发送 (ixgbe_xmit_frame) ⑦ DMA → 网卡发送 虚线连接表示同一层次的对等处理;实际路径中 sk_buff 在层间传递 Netfilter 钩子分布在步骤 ④⑤⑥ 中,是性能优化的关键点

2.4 协议栈性能开销分析

理解协议栈的数据路径后,可以量化其主要性能开销来源。下表总结了各层的典型开销:

开销来源 所在层次 典型耗时 (CPU cycles) 优化方向
DMA 中断处理 驱动层 500-2000 NAPI 轮询、中断合并
sk_buff 分配与释放 驱动层 200-500 slab 缓存、预分配
Netfilter 钩子遍历 网络层 1000-5000 规则精简、nftables
路由查找 网络层 300-800 路由缓存、FIB 优化
TCP 校验和计算 传输层 200-600 硬件 offload
用户态数据拷贝 系统调用层 1000-3000 mmap、零拷贝
上下文切换 系统调用层 2000-5000 批处理、busy polling
实践建议: 在评估 KNI 性能时,可以将上述开销作为基线。KNI 通过绕过部分内核路径来降低延迟,但同时也引入了 kni_net_rx 和 kni_net_tx 的额外处理开销。后续章节将详细对比这些差异。