1. 准备阶段:驱动加载与Ring Buffer
在数据包真正到达之前,操作系统和网卡必须做好准备。
- 驱动初始化:当网卡驱动加载时,它会初始化硬件,并申请一个名为 Ring Buffer (环形缓冲区) 的内存区域(在RAM中)。
- 描述符映射:Ring Buffer中存放的不是实际数据包,而是描述符(Descriptor)。每个描述符指向内核内存中预先分配好的 sk_buff(Socket Buffer,Linux网络核心数据结构)的数据区域。
- DMA映射:驱动程序将这些内存地址告诉网卡硬件,建立了DMA(直接内存访问)映射。此时,网卡知道当数据到来时应该写到内存的哪个位置。
2. 硬件阶段:物理信号与DMA
当网线或光纤上传来电信号/光信号时:
- PHY/MAC层处理:网卡的物理层(PHY)将信号转换为数字信号,MAC层进行CRC校验和帧完整性检查。如果校验失败,包会被直接丢弃(体现在网卡错误计数中)。
- DMA传输:网卡硬件通过DMA控制器,将接收到的数据包直接写入之前在RAM中分配好的 sk_buff 数据区,不需要CPU参与。
- 更新指针:网卡修改Ring Buffer中的描述符状态,标记为“由硬件持有”变为“由CPU持有”,表示数据已就绪。
3. 内核中断阶段:硬中断与NAPI
数据进入内存后,CPU还不知道。通知CPU的过程如下:
- 触发硬中断 (Hard IRQ):网卡向CPU发起一个硬件中断信号。
- 中断处理程序:CPU暂停当前任务,根据中断号找到对应的网卡驱动注册的中断处理函数。
- 屏蔽中断与调度:
- 在现代Linux驱动(采用 NAPI, New API 机制)中,硬中断处理函数做的事情非常少:它首先屏蔽该网卡的接收中断(防止在处理大量数据包时CPU被频繁打断而活活累死)。
- 然后,它发起一个 软中断 (Soft IRQ),具体是 NET_RX_SOFTIRQ。
- 最后,硬中断处理结束,CPU返回。
核心点:NAPI机制的核心思想是“中断+轮询”。第一包数据触发中断,随后关闭中断进入轮询模式,直到所有数据处理完再重新开启中断。
4. 协议栈处理阶段:软中断与分层解析
这是流程中最复杂的部分,通常由内核线程 ksoftirqd 或在系统调用返回前夕处理。
A. 软中断入口 (L2 - 链路层)
- 执行软中断:内核运行 net_rx_action 函数。
- NAPI Poll:net_rx_action 调用网卡驱动注册的 poll 函数。
- 摘取数据:poll 函数从Ring Buffer中取下数据包,封装成完整的 sk_buff 结构体。
- GRO (Generic Receive Offload):如果开启了GRO,内核会尝试在这一层将多个小的TCP包合并成一个大包,以减少后续协议栈的处理开销。
- RPS (Receive Packet Steering):(可选) 如果配置了RPS,内核会通过哈希计算将包分发给其他CPU处理,实现软中断负载均衡。
- 提交给协议栈:调用 netif_receive_skb (或 __netif_receive_skb_core),将包交给上层。
B. 网络层 (L3 - IP层)
- 协议分发:根据以太网头部的 EtherType (如 0x0800 代表 IPv4),数据包被分发给 ip_rcv 函数。
- Netfilter (PREROUTING):数据包经过 iptables/nftables 的 PREROUTING 链。如果被防火墙规则 DROP,流程在此终止。
- 路由查找:内核查询路由表。
- 如果目的IP不是本机,且允许转发,则进入转发路径 (ip_forward)。
- 如果目的IP是本机,则调用 ip_local_deliver。
- Netfilter (INPUT):经过 INPUT 链过滤。
- 分片重组:如果IP包是分片的,内核会在此处进行重组。
- 传给传输层:根据IP头部的协议字段(如 TCP=6, UDP=17),调用相应的处理函数(如 tcp_v4_rcv)。
C. 传输层 (L4 - TCP/UDP层)
以TCP为例 (tcp_v4_rcv):
- 校验:检查TCP头部校验和。
- 查找Socket:根据 <源IP, 源端口, 目的IP, 目的端口> 四元组,在内核的哈希表中查找对应的 struct sock (Socket结构体)。如果不复存在,回复RST包。
- 协议处理:处理TCP状态机(如处理ACK、更新滑动窗口、处理重传等)。
- 放入接收队列:如果数据是按序到达的,内核将 sk_buff 放入该Socket的 接收队列 (Receive Queue, sk_receive_queue) 中。
- 唤醒进程:内核调用 sk_data_ready,唤醒等待在该Socket上的用户进程(如阻塞在 read 的进程,或通知 epoll 事件)。
5. 用户态交互阶段:系统调用与数据拷贝
此时数据还在内核内存中,用户进程开始介入。
- 系统调用:用户进程调用 read(), recv(), 或 recvfrom() 等系统调用。
- 上下文切换:CPU从用户态切换到内核态。
- 拷贝数据:内核将数据从内核空间的 sk_buff 拷贝到用户程序提供的缓冲区(User Buffer)中。
- 释放内存:拷贝完成后,内核释放 sk_buff 及其占用的Ring Buffer内存(实际上是将描述符交还给网卡,供下次DMA使用)。
- 返回:系统调用返回,用户进程读取到数据,流程结束。