网卡收发包流程

讲一下网卡收包流程(gemini)

网络适配器(网卡/NIC)收包是一个从物理信号转换为内核数据包的过程。这个过程涉及硬件、驱动、内核协议栈的紧密配合。

为了让你更直观地理解,我们可以将这个过程分为四个主要阶段:

1. 物理层与链路层处理 (硬件阶段)
当数据包通过光纤或双绞线到达网卡时,网卡首先进行初步处理:

信号转换: 网卡将物理电信号/光信号转换为数字信号。

校验与过滤: * 检查 FCS (帧校验序列),如果出错则丢弃。

检查 MAC 地址,看是否发给本机的(或是广播/组播),如果不匹配且未开启混杂模式,则丢弃。

DMA 传输: 这是关键的一步。网卡不会等 CPU 来拿数据,而是通过 DMA (Direct Memory Access) 直接将数据包写入主机内存中的 Ring Buffer (环形缓冲区)。

2. 中断触发 (硬件/驱动交互)
一旦数据包被写入内存,网卡需要通知系统“活儿来了”:

硬件中断 (Hard IRQ): 网卡向 CPU 发送一个硬件中断信号。

中断处理: CPU 暂停当前任务,执行网卡驱动注册的中断处理函数。

NAPI 机制: 现代 Linux 内核通常使用 NAPI (New API)。为了防止高并发下的“中断风暴”,硬件中断只负责:

禁用网卡的收包中断。

调度 软中断 (Softirq) 进入轮询模式(Polling)。

3. 内核协议栈处理 (软件阶段)
此时,内核开始接管内存中的原始数据:

软中断处理: 内核线程(如 ksoftirqd)调用驱动的 poll 函数,从 Ring Buffer 中读取数据。

封装为 sk_buff: 驱动程序将原始数据包装成内核通用的数据结构 sk_buff。

进入协议栈: * Layer 2 (链路层): 处理以太网头部,检查 VLAN 等信息。

Layer 3 (网络层): 进入 ip_rcv。检查 IP 校验和、进行路由查找。如果是发给本机的,则继续向上。

Layer 4 (传输层): 进入 tcp_v4_rcv 或 udp_rcv。根据端口号找到对应的 Socket。

4. 用户态交付 (应用阶段)
最后,数据包从内核空间搬运到用户空间:

唤醒进程: 当数据到达 Socket 的接收缓冲区时,内核唤醒正在 read 或 recv 该 Socket 的进程。

系统调用返回: 数据从内核缓冲拷贝到用户缓冲区,系统调用返回,应用程序正式拿到数据。

总结
简单来说,收包流程就像快递送货:

网卡 (快递员):把货卸在仓库 (Ring Buffer) 门口。

中断 (门铃):快递员按一下门铃告诉主人有货。

驱动/协议栈 (管家):管家把货拆箱、检查地址、分拣到不同的房间 (Socket)。

应用 (主人):主人从房间里把东西拿出来使用。

技术点拨:

如果在高负载下出现丢包,通常有两个排查点:一个是网卡的 Ring Buffer 满了(硬件驱动层),另一个是内核的 Socket Buffer 满了(应用处理太慢)。

你想深入了解一下其中 NAPI 轮询的具体细节,还是想看看如何通过 ethtool 监控这些缓冲区的状态?

netdev_max_backlog和这里面的那一步相关?

netdev_max_backlog 主要关联的是 第 2 阶段(中断/NAPI) 到 第 3 阶段(内核协议栈) 之间的过渡区。

具体来说,它作用于内核处理数据包的一个“中转站”:CPU 输入队列(Input Packet Queue)。

它的具体位置
在 Linux 网络子系统中,当网卡驱动程序(通过 NAPI)将数据包从硬件缓存读取出来,并准备交给协议栈处理时,如果 接收数据的 CPU 核心处理速度跟不上网卡进包的速度,内核会将这些 sk_buff 暂时存放在一个每个 CPU 独有的队列中。

这个队列的最大长度限制就是由 netdev_max_backlog 控制的。

详细处理流程
我们可以把这个参数看作是内核协议栈入口处的一个“蓄水池”:

软中断触发: NAPI 机制从网卡的 Ring Buffer 中取出包。

数据排队: 内核调用 netif_rx() 或类似的函数,试图将包放入当前 CPU 的等待队列。

容量检查:

如果当前队列中的包数量 < netdev_max_backlog,数据包成功入队,等待协议栈逐层解析。

如果队列已满(数量 >= netdev_max_backlog),内核会直接将新来的包丢弃(Drop)。

丢包统计: 此时,你会在 /proc/net/softnet_stat 中看到对应的丢包计数增加。

为什么要关注这个参数?
缓解突发流量: 当网络出现瞬间的高峰流量(Micro-bursts)时,调大此参数可以让系统有更多的缓冲空间,避免在还没进入协议栈之前就掉包。

多核处理差异: 因为它是按 CPU 核心分配的,如果系统的中断负载不均衡(比如所有中断都压在 CPU0 上),即使全局 CPU 利用率不高,也可能因为 CPU0 的队列满了而导致丢包。

如何查看与修改?
查看当前配置:

Bash
sysctl net.core.netdev_max_backlog
临时修改(例如改为 5000):

Bash
sudo sysctl -w net.core.netdev_max_backlog=5000
监控状态:
你可以查看 /proc/net/softnet_stat。该文件的第二列如果一直在增长,通常就意味着 netdev_max_backlog 设小了,导致了丢包。