2. Linux 网络协议栈基础回顾:内核网络栈分层架构
在深入 TCP/IP 卸载与硬件加速之前,必须重新审视 Linux 内核网络协议栈的经典分层模型。理解每一层的职责、数据流走向以及层间交互的代价,是后续配置硬件卸载(如 TSO、GSO、RSS、XDP)的理论基础。本章从 Socket 层向下至驱动层,逐层拆解其核心功能与数据结构。
2.1 分层全景:从系统调用到网线
Linux 内核网络栈遵循 BSD 套接字模型,自上而下分为五个主要层次。每一层对上层提供抽象,对下层屏蔽细节,数据以 struct sk_buff(套接字缓冲区)为载体在层间传递。
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。
2.5 Netfilter 框架:钩子与包过滤
Netfilter 是 Linux 内核内置的包处理框架,允许内核模块在协议栈的关键路径上注册钩子函数。iptables / nftables 均基于此框架实现。
Netfilter 在 IPv4 路径上定义了五个标准钩子点:
- NF_IP_PRE_ROUTING: 数据包进入协议栈后、路由决策之前。
- NF_IP_LOCAL_IN: 路由决策后,发往本机的数据包。
- NF_IP_FORWARD: 路由决策后,需要转发的数据包。
- NF_IP_LOCAL_OUT: 本机发出的数据包,在路由决策之前。
- NF_IP_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 指针,避免数据拷贝。这种零拷贝设计是内核网络栈高性能的关键之一。
sk_buff 中的 gso_size 和 gso_segs 字段会告知网卡如何分段。理解这些字段的赋值时机,是配置卸载功能的前提。