2. 网络协议栈分层架构:七层/四层模型映射与 sk_buff 核心作用

在分析网络丢包之前,必须理解 Linux 内核网络协议栈的层次结构。实际的内核实现并非严格遵循 OSI 七层模型,而是更贴近 TCP/IP 四层模型,并在驱动层和套接字层之间进行了高效的融合。本章将详细拆解这种映射关系,并深入剖析贯穿整个协议栈的核心数据结构——sk_buff。

2.1 OSI 七层模型与 Linux 内核四层模型的映射

Linux 内核网络协议栈的实现是务实的。它将 OSI 七层模型中的会话层、表示层和应用层合并为应用层,并将物理层和数据链路层合并为网络接口层。下表展示了标准模型与内核实现之间的精确对应关系:

OSI 七层模型 TCP/IP 四层模型 Linux 内核对应模块/层 核心功能
应用层 (Application) 应用层 系统调用接口 (Socket Layer) 提供 read()、write()、sendmsg()、recvmsg() 等接口
表示层 (Presentation) 套接字通用管理层 (Protocol Family) 处理 struct socket 和 struct sock 的通用逻辑
会话层 (Session) 具体协议实现 (TCP/UDP) TCP 状态机、拥塞控制、UDP 数据报处理
传输层 (Transport) 传输层 传输层协议模块 (TCP/UDP/SCTP) 端口寻址、分段重组、连接管理
网络层 (Network) 网络层 网络层协议模块 (IPv4/IPv6) 路由查找、分片与重组、IP 选项处理
数据链路层 (Data Link) 网络接口层 网络设备子系统 (LL层) 邻居子系统 (ARP)、流量控制、dev_queue_xmit()
物理层 (Physical) 网卡驱动 (Hardware Driver) DMA 传输、中断处理、NAPI 轮询
丢点分析提示: 丢包可能发生在上述任何一个层次。例如,应用层的套接字接收缓冲区满会导致数据被丢弃;网络层的 IP 分片重组超时会导致分片丢失;数据链路层的环形队列 (Ring Buffer) 满会导致驱动层丢包。理解分层是定位丢包点的第一步。

2.2 内核协议栈数据流与分层处理 SVG 架构图

下图展示了数据包从网卡接收,经过各层处理,最终到达用户态应用程序的完整路径。每个方框代表一个关键处理阶段,箭头表示数据流方向。

Linux 内核网络协议栈数据流与分层处理架构 用户态应用程序 (User Space) 系统调用接口 (Socket Layer) 传输层 (TCP/UDP 协议处理) 端口查找 · 分段重组 · 拥塞控制 网络层 (IPv4/IPv6 路由) 路由查找 · IP 分片 · 防火墙 (Netfilter) 数据链路层 (邻居子系统 & 流量控制) ARP 解析 · Qdisc 队列 · dev_queue_xmit() 网卡驱动层 (Hardware Driver) DMA 传输 · 中断处理 · NAPI 轮询 物理网卡硬件 (NIC) 数据流方向说明 左侧箭头 (实线) 表示数据接收路径:NIC → 驱动 → 链路层 → 网络层 → 传输层 → 套接字 → 用户态 右侧箭头 (虚线) 表示数据发送路径:用户态 → 套接字 → 传输层 → 网络层 → 链路层 → 驱动 → NIC

2.3 sk_buff 结构体:贯穿协议栈的“快递包裹”

sk_buff(Socket Buffer)是 Linux 网络子系统中最核心的数据结构。它从驱动层接收数据开始被创建,一直传递到套接字层,甚至可以被克隆或共享。可以将其理解为在协议栈各层之间传递的“快递包裹”,包裹内部包含了数据本身以及各层处理所需的“元数据”。

2.4 sk_buff 核心字段与分层作用

以下表格列出了 sk_buff 中与丢包分析最密切相关的字段,以及它们在各层中的作用:

字段名称 数据类型 主要作用层 丢包分析关联
dev struct net_device * 驱动层 / 链路层 记录接收或发送的网络设备。若为 NULL,可能导致路由丢包。
sk struct sock * 传输层 / 套接字层 指向拥有此缓冲区的套接字。若为 NULL,数据包可能因找不到对应 socket 而被丢弃。
data / head / end unsigned char * 所有层 指向数据缓冲区。各层通过调整 data 指针来剥离或添加协议头。
len unsigned int 所有层 当前有效数据长度。长度异常(如小于头部长度)是丢包的直接原因。
protocol __be16 链路层 / 网络层 标识上层协议(如 ETH_P_IP、ETH_P_ARP)。不匹配会导致协议处理模块拒绝接收。
pkt_type __u8 链路层 标识包类型(单播、多播、广播、其他主机)。非本机单播包会被丢弃。
ip_summed __u8 驱动层 / 网络层 校验和状态。若硬件未计算校验和且软件校验失败,包将被丢弃。
tstamp ktime_t 驱动层 / 套接字层 时间戳。用于 RTT 计算和超时重传,超时丢包与此相关。
destructor void (*)(struct sk_buff *) 套接字层 释放缓冲区时的回调函数。用于管理内存和统计丢包计数。

2.5 sk_buff 在各层的操作:指针移动与数据封装

sk_buff 的精妙之处在于,它通过移动 data 指针来避免数据拷贝。发送数据时,各层在头部预留空间;接收数据时,各层逐层剥离头部。

关键操作函数:
  • skb_push():将 data 指针向上移动,在数据包头部添加新协议头(发送路径)。
  • skb_pull():将 data 指针向下移动,剥离当前层协议头(接收路径)。
  • skb_put():将 tail 指针向下移动,在数据包尾部添加数据。
  • skb_trim():将 tail 指针向上移动,截断数据包尾部。

2.6 sk_buff 在接收路径中的指针变化 SVG 示意图

下图展示了一个以太网帧从驱动层到达传输层时,sk_buff 中 data 指针的移动过程。灰色区域表示已剥离的头部,蓝色区域表示当前层可见的数据。

sk_buff 在接收路径中的 data 指针移动 (头部剥离过程) 阶段1: 驱动层 (DMA 完成) L2头 L3头 + L4头 + 载荷数据 data 指针 阶段2: 链路层 (剥离 L2 头后) 已剥离 L3头 + L4头 + 载荷数据 data 指针 阶段3: 网络层 (剥离 L3 头后) 已剥离 已剥离 L4头 + 载荷数据 data 指针 阶段4: 传输层 (剥离 L4 头后) 已剥离 已剥离 已剥离 载荷数据 (Payload) data 指针

2.7 丢包分析中的 sk_buff 关键检查点

在实际的丢包排查中,可以通过 ftrace 或 kprobe 动态追踪 sk_buff 的释放事件。以下是一些常见的丢包场景与 sk_buff 状态的关系:

  • 驱动层丢包: 检查 skb->len 是否小于以太网帧最小长度 (60字节),或 skb->ip_summed 是否为 CHECKSUM_UNNECESSARY 但硬件校验失败。
  • 网络层丢包: 检查 skb->protocol 是否与预期协议匹配;检查路由查找结果,若 skb->dev 指向的出口设备为 NULL,则发生路由丢包。
  • 传输层丢包: 检查 skb->sk 是否为 NULL(孤儿包);检查接收队列是否已满(通过 skb->destructor 回调统计)。
  • 套接字层丢包: 检查套接字接收缓冲区是否达到 sk_rcvbuf 上限,此时 skb 会被直接丢弃并调用 kfree_skb()。
性能调优警告: 频繁的 sk_buff 分配和释放会导致 slab 缓存抖动。在生产环境中,建议启用 CONFIG_SLUB 并监控 /proc/slabinfo 中的 skbuff_head_cache 使用情况。若该缓存对象数量持续增长,说明协议栈某处存在内存泄漏或丢包后未正确释放缓冲区。

2.8 本章小结