2. Linux 网络协议栈基础回顾:内核网络栈分层架构

在深入 TCP/IP 卸载与硬件加速之前,必须重新审视 Linux 内核网络协议栈的经典分层模型。理解每一层的职责、数据流走向以及层间交互的代价,是后续配置硬件卸载(如 TSO、GSO、RSS、XDP)的理论基础。本章从 Socket 层向下至驱动层,逐层拆解其核心功能与数据结构。

2.1 分层全景:从系统调用到网线

Linux 内核网络栈遵循 BSD 套接字模型,自上而下分为五个主要层次。每一层对上层提供抽象,对下层屏蔽细节,数据以 struct sk_buff(套接字缓冲区)为载体在层间传递。

关键认知: 协议栈的每一层都会引入 CPU 开销(校验和计算、内存拷贝、上下文切换)。硬件卸载的本质是将这些开销从 CPU 转移到网卡硬件,从而释放处理器资源。
Linux 内核网络协议栈分层架构 Linux 内核网络协议栈分层架构 Socket 层 (System Call Interface) sys_socket / sys_sendmsg / sys_recvmsg / VFS 抽象 传输层 (Transport Layer) TCP: 面向连接、可靠、流量控制、拥塞控制 UDP: 无连接、尽力交付、低延迟 网络层 (Network Layer) IPv4 / IPv6: 路由查找、分片与重组、L3 转发 邻居子系统 (ARP / NDISC) Netfilter 框架 (钩子与规则匹配) NF_IP_PRE_ROUTING / LOCAL_IN / FORWARD / LOCAL_OUT / POST_ROUTING 驱动层 (NIC Driver & DMA) NAPI / 中断处理 / TX/RX 描述符环 / DMA 引擎

2.2 Socket 层:用户态与内核态的桥梁

Socket 层是用户态应用程序与内核协议栈之间的通用接口。它通过 VFS(虚拟文件系统)将网络套接字抽象为文件描述符,使得 read() / write() 系统调用可以作用于网络 I/O。

  • 核心数据结构: struct socket 和 struct sock。前者是 VFS 层抽象,后者是传输层私有状态。
  • 关键操作: sys_socket() 创建套接字,sys_connect() / sys_bind() / sys_listen() 建立连接,sys_sendmsg() / sys_recvmsg() 传输数据。
  • 协议族注册: 通过 net_families[] 数组注册 AF_INET、AF_INET6、AF_PACKET 等协议族。
性能提示: 每次系统调用都会触发用户态/内核态切换。对于高频小包场景,建议使用 recvmmsg() / sendmmsg() 批量系统调用,或采用 DPDK / XDP 绕过内核。

2.3 传输层:TCP 与 UDP 的分工

传输层为上层应用提供端到端通信能力。TCP 和 UDP 在协议栈中的处理路径差异显著,直接影响卸载策略的选择。

特性 TCP UDP
连接状态 面向连接(三次握手) 无连接
可靠性 确认重传、序列号、校验和 无确认、无重传
流量控制 滑动窗口、拥塞控制(CUBIC/BBR) 无
内核开销 高(定时器、重传队列、拥塞状态机) 低(无状态维护)
硬件卸载适用性 TSO、GSO、LRO、GRO、校验和卸载 UFO、GSO、校验和卸载

TCP 处理路径中,tcp_v4_rcv() 负责查找已建立的 socket,tcp_v4_do_rcv() 处理数据段,tcp_rcv_established() 执行快速路径或慢速路径。UDP 则通过 udp_rcv() 直接查找 socket 并交付数据。

2.4 网络层:路由与 IP 处理

网络层负责 IP 数据报的收发、路由查找、分片与重组。核心入口函数为 ip_rcv()(接收)和 ip_output()(发送)。

  • 路由子系统: 通过 FIB(Forwarding Information Base)和路由缓存(早期内核)或 LPM trie(当前内核)进行最长前缀匹配。
  • 分片与重组: ip_fragment() 在 MTU 超限时执行分片;ip_defrag() 在接收端重组分片包。
  • 邻居子系统: 负责 L3 到 L2 地址解析(ARP for IPv4,NDISC for IPv6),维护邻居缓存 struct neighbour。
卸载冲突注意: 启用硬件 TSO(TCP Segmentation Offload)时,驱动层会发送超大 TSO 报文,IP 层不会对单个 TSO 报文执行分片。如果网卡不支持 TSO 分片卸载,可能导致 MTU 超限丢包。

2.5 Netfilter 框架:钩子与包过滤

Netfilter 是 Linux 内核内置的包处理框架,允许内核模块在协议栈的关键路径上注册钩子函数。iptables / nftables 均基于此框架实现。

Netfilter 在 IPv4 路径上定义了五个标准钩子点:

  1. NF_IP_PRE_ROUTING: 数据包进入协议栈后、路由决策之前。
  2. NF_IP_LOCAL_IN: 路由决策后,发往本机的数据包。
  3. NF_IP_FORWARD: 路由决策后,需要转发的数据包。
  4. NF_IP_LOCAL_OUT: 本机发出的数据包,在路由决策之前。
  5. NF_IP_POST_ROUTING: 本机发出的数据包,在路由决策之后、离开协议栈之前。
Netfilter 钩子点与数据流 Netfilter 钩子点与数据流(IPv4) 入口 (NIC) PRE_ROUTING 路由决策 LOCAL_IN 本机进程 FORWARD POST_ROUTING

2.6 驱动层:NAPI 与 DMA 引擎

驱动层是协议栈与物理网卡之间的最后一道关卡。现代网卡驱动普遍采用 NAPI(New API)机制,结合中断与轮询,在高速收包场景下减少中断风暴。

组件 功能描述 性能影响
TX/RX 描述符环 驱动与网卡共享的环形缓冲区,存放 DMA 地址和长度 环大小影响吞吐与延迟
DMA 引擎 网卡直接读写主机内存,无需 CPU 介入 减少 CPU 拷贝,降低延迟
NAPI 中断开启 → 收包 → 关闭中断 → 轮询收包 → 重新开启中断 高负载下减少中断次数,提升吞吐
硬件卸载引擎 校验和卸载、TSO/GRO、RSS、Flow Director 显著降低 CPU 占用

驱动层通过 net_device_ops 结构体向内核注册 ndo_start_xmit()(发送)和 NAPI 轮询函数 poll()(接收)。数据包在驱动层以 struct sk_buff 形式传递给上层协议栈。

实践要点: 在数据中心环境中,建议启用 gro(Generic Receive Offload)和 gso(Generic Segmentation Offload),它们在内核层面模拟硬件卸载,即使网卡不支持硬件 TSO/GRO 也能获得性能提升。

2.7 层间数据传递:sk_buff 的生命周期

struct sk_buff 是贯穿整个协议栈的核心数据结构。它包含头部指针(head、data、tail、end)、协议信息(protocol)、路由信息(dst)以及各层私有控制块(cb[])。

// sk_buff 关键字段示意(简化)
struct sk_buff {
    /* 缓冲区指针 */
    unsigned char   *head;      // 分配内存起始
    unsigned char   *data;      // 当前协议层数据起始
    unsigned char   *tail;      // 当前协议层数据结束
    unsigned char   *end;       // 分配内存结束

    /* 协议信息 */
    __be16          protocol;   // L3 协议类型 (ETH_P_IP / ETH_P_IPV6)

    /* 网络层与传输层信息 */
    struct net_device   *dev;   // 关联网络设备
    struct dst_entry    *dst;   // 路由缓存条目

    /* 各层私有控制块 */
    char            cb[48] __aligned(8);  // 传输层/网络层私有数据

    /* 校验和与卸载标志 */
    __u32           csum;       // 校验和值
    __u8            ip_summed;  // 校验和类型 (CHECKSUM_NONE / CHECKSUM_UNNECESSARY / CHECKSUM_COMPLETE / CHECKSUM_PARTIAL)

    /* 分段卸载标志 */
    __u16           gso_size;   // GSO 分段大小
    __u16           gso_segs;   // GSO 分段数量
    __u8            gso_type;   // GSO 类型 (SKB_GSO_TCPV4 / SKB_GSO_UDP / ...)
};

数据包在层间传递时,通过 skb_push()(添加头部)、skb_put()(扩展数据)、skb_pull()(移除头部)调整 data 和 tail 指针,避免数据拷贝。这种零拷贝设计是内核网络栈高性能的关键之一。

进阶思考: 硬件卸载(如 TSO)在驱动层将大 TCP 报文拆分为多个 MTU 大小的段,但 sk_buff 中的 gso_size 和 gso_segs 字段会告知网卡如何分段。理解这些字段的赋值时机,是配置卸载功能的前提。