2. Linux 网络协议栈分层模型

理解网卡驱动发包流程,首先需要掌握网络协议栈的分层架构。分层设计是计算机网络与操作系统内核网络子系统的核心思想,它将复杂的网络通信过程分解为多个职责清晰的层次,每一层只关注特定的功能,并通过标准接口与相邻层交互。

2.1 OSI 七层模型与 TCP/IP 四层模型

在理论教学与工程实践中,最常引用的两个分层模型是 OSI(开放系统互连)七层模型 和 TCP/IP 四层模型。OSI 模型是国际标准化组织制定的概念性框架,而 TCP/IP 模型则是实际互联网所采用的协议栈。

学习提示: 在 Linux 内核开发中,我们主要关注 TCP/IP 四层模型,但 OSI 模型的“会话层”和“表示层”功能在应用层协议(如 HTTP、TLS)中实现,理解 OSI 有助于建立完整的网络知识体系。
OSI 七层模型 TCP/IP 四层模型 核心功能 Linux 内核对应模块
7. 应用层 应用层 为用户应用提供网络服务(HTTP、FTP、DNS) 用户空间(socket 接口之上)
6. 表示层 数据格式转换、加密、压缩 用户空间库(如 OpenSSL)
5. 会话层 建立、管理、终止会话连接 用户空间应用逻辑
4. 传输层 传输层 端到端可靠传输(TCP)、不可靠传输(UDP) TCP/IP 协议栈(tcp_ipv4.c, udp.c)
3. 网络层 网络层 路由选择、分组转发、IP 寻址 IP 协议栈(ip_output.c, ip_forward.c)
2. 数据链路层 网络接口层 帧封装、MAC 寻址、差错检测 网络设备子系统(dev.c, net/core/)
1. 物理层 网络接口层 比特流传输、电气信号、介质访问 网卡驱动(drivers/net/)
关键理解: 在 Linux 内核中,TCP/IP 四层模型被进一步细化为内核网络子系统。物理层和数据链路层的功能由网卡硬件和其驱动程序共同完成;网络层和传输层由内核协议栈软件实现;应用层则运行在用户空间。
OSI 七层模型 vs TCP/IP 四层模型 7. 应用层 6. 表示层 5. 会话层 4. 传输层 3. 网络层 2. 数据链路层 1. 物理层 应用层 传输层 网络层 网络接口层 层次对应关系 • OSI 5-7 → TCP/IP 应用层 • OSI 4 → TCP/IP 传输层 • OSI 3 → TCP/IP 网络层 • OSI 1-2 → TCP/IP 网络接口层 Linux 内核实现范围: • 传输层 + 网络层:内核协议栈 • 网络接口层:设备子系统 + 驱动 • 物理层:网卡硬件 本课程聚焦: 网络接口层 → 物理层

2.2 Linux 内核网络子系统的分层架构

Linux 内核网络子系统在 TCP/IP 模型的基础上,进一步划分出清晰的软件层次。从用户态应用发送数据到网卡发出数据帧,数据依次经过以下核心层次:

  1. 系统调用层(System Call Interface):用户程序通过 send()、write() 等系统调用进入内核。
  2. Socket 层:通用 socket 接口,负责管理 socket 数据结构,将操作分发到具体的协议族(如 AF_INET)。
  3. 传输层(TCP/UDP):处理分段、可靠传输、流量控制、校验和计算。
  4. 网络层(IP):路由查找、分片、IP 头封装、邻居子系统(ARP)交互。
  5. 网络设备子系统(Net Device Subsystem):核心调度层,管理 sk_buff 队列,调用驱动发送接口。
  6. 网卡驱动层(NIC Driver):将 sk_buff 映射为 DMA 描述符,触发硬件发送。
  7. 物理层(PHY / Hardware):网卡硬件通过 DMA 读取数据,经 MAC/PHY 发送到网线。
常见误区: 很多开发者认为数据从用户态到网卡是“直线下降”的。实际上,Linux 网络子系统在每一层都可能将数据包入队(如 qdisc 队列、backlog 队列),存在多次调度和排队过程,这正是本课程后续章节要深入剖析的“队列调度”核心。
Linux 内核网络子系统分层架构(发包路径) 用户空间应用 (send/write) 系统调用层 (sys_sendto / sys_write) Socket 层 (sock_sendmsg / inet_sendmsg) 传输层 (tcp_sendmsg / udp_sendmsg) 网络层 (ip_queue_xmit / ip_output) 邻居子系统 (neigh_output / ARP) 网络设备子系统 (dev_queue_xmit / qdisc) 网卡驱动层 (ndo_start_xmit / DMA) 物理层 (网卡硬件 / PHY / 网线) 层次职责 • 系统调用:用户→内核切换 • Socket:通用接口抽象 • 传输层:TCP/UDP 协议处理 • 网络层:路由 + IP 封装 • 邻居:MAC 地址解析 • 设备子系统:队列调度 • 驱动:DMA + 寄存器操作 • 硬件:物理信号发送 本课程重点区域: 设备子系统 → 驱动 → 硬件 (即绿色 + 红色 + 紫色区域) 核心数据结构: sk_buff, net_device, Qdisc, NAPI 结构

2.3 各层核心数据结构概览

在 Linux 内核网络子系统中,每一层都有其核心的数据结构,这些结构是数据包在内核中流转的载体。理解这些结构是掌握驱动发包流程的前提。

层次 核心数据结构 定义位置(内核源码) 关键字段 / 作用
Socket 层 struct socket include/linux/net.h file(关联文件描述符)、ops(协议操作表)
Socket 层 struct sock include/net/sock.h sk_receive_queue、sk_write_queue、sk_prot(协议函数指针)
传输层 struct tcp_sock include/linux/tcp.h tcp_header_len、write_seq、snd_cwnd(拥塞窗口)
网络层 struct iphdr include/uapi/linux/ip.h version、ihl、saddr、daddr、protocol
网络层 struct rtable include/net/route.h 路由缓存条目,包含下一跳、输出设备指针
邻居子系统 struct neighbour include/net/neighbour.h ha(硬件 MAC 地址)、dev(输出设备)、ops
设备子系统 struct net_device include/linux/netdevice.h name、mtu、flags、netdev_ops(驱动操作表)
设备子系统 struct Qdisc include/net/sch_generic.h enqueue、dequeue 函数指针,流量控制核心
通用数据包 struct sk_buff include/linux/skbuff.h 贯穿所有层次的核心数据包结构(详见后续章节)
驱动层 struct net_device_ops include/linux/netdevice.h ndo_start_xmit(驱动发送入口)、ndo_open、ndo_stop
驱动层 struct skb_shared_info include/linux/skbuff.h 管理 sk_buff 的 frags(分散/聚合 I/O 片段)
核心要点: struct sk_buff 是贯穿整个网络子系统的“通用货币”。从用户数据到驱动发送,每一层都在操作 sk_buff 的头部指针(head、data、tail、end)来添加或剥离协议头。驱动层的 ndo_start_xmit 函数接收的就是一个完整的、已封装好所有协议头的 sk_buff。
各层核心数据结构关系图 用户空间应用 struct socket / sock struct tcp_sock struct iphdr / rtable struct net_device / Qdisc struct net_device_ops sk_buff:贯穿所有层次的核心载体 struct sk_buff head → data → tail → end 指针管理协议头 各层通过移动 data 指针添加/剥离协议头: 1. 传输层:skb_push(skb, TCP_HLEN) 添加 TCP 头 2. 网络层:skb_push(skb, IP_HLEN) 添加 IP 头 3. 邻居层:填充 MAC 地址到 skb->data 4. 设备层:skb->dev 指向输出 net_device 5. 驱动层:读取 skb->data 和 skb->len 映射到 DMA 描述符

2.4 数据包在内核中的封装过程

当用户通过 socket 发送数据时,内核协议栈会逐层为原始数据添加协议头部。这个过程称为“封装”(Encapsulation)。理解封装顺序对于调试驱动发包问题至关重要。

调试技巧: 使用 ftrace 或 bpftrace 跟踪 skb_push() 和 skb_put() 的调用,可以精确观察每一层如何修改 sk_buff 的指针。这是分析协议栈行为的利器。

典型 TCP 数据包封装流程(从用户数据到网卡帧):

  1. 应用层数据:用户缓冲区中的原始数据(如 HTTP POST 内容)。
  2. Socket 层:将用户数据拷贝到内核 sk_buff 的数据区(skb_put())。
  3. 传输层(TCP):skb_push() 添加 TCP 头部(20~60 字节),包含源端口、目的端口、序列号、确认号、窗口大小等。
  4. 网络层(IP):skb_push() 添加 IP 头部(20~60 字节),包含源 IP、目的 IP、协议号(TCP=6)、TTL 等。
  5. 邻居子系统:通过 ARP 或邻居缓存获取目标 MAC 地址,填充到 skb->data 之前(通过 skb_push() 添加以太网头)。
  6. 网络设备子系统:将 sk_buff 入队到 qdisc 队列,等待调度。
  7. 网卡驱动:从 qdisc 出队 sk_buff,将其 DMA 映射到硬件描述符,触发发送。
数据包封装过程(逐层添加协议头) 用户数据 (Payload) ← 上一章 📖 返回目录 下一章 →