Linux 网络数据包流转
整个收发包流程的核心在于数据如何在 用户态 (User Space)、内核态 (Kernel Space) 和 硬件 (Hardware) 之间流转。
发送 (TX) 和 接收 (RX) 流程如下:
一、 数据发送流程 (TX: Transmit)
从应用层 write() 到 网卡发送
-
应用层 (User Space)
-
应用程序(例如你的 Go 程序)调用系统调用,如
write(fd, buffer)或sendto()。 -
关键动作:内存拷贝。数据从用户态内存(User Buffer)被拷贝到内核态内存(Kernel Memory),此时数据被封装成 Linux 网络核心数据结构 ——
sk_buff(Socket Buffer)。
-
-
传输层 (Transport Layer - TCP/UDP)
-
内核根据 Socket 类型(TCP/UDP)处理数据。
-
TCP: 分段 (Segmentation)、计算校验和、处理序列号、滑动窗口。
-
UDP: 直接添加 UDP 头。
-
此时
sk_buff增加了 L4 头部。
-
-
网络层 (Network Layer - IP)
-
查找路由表:决定数据包去往哪里(下一跳网关或本机)。
-
Netfilter (iptables/nftables): 经过
OUTPUT链,防火墙规则在这里生效。 -
IP 头部被添加。如果有必要,进行 IP 分片。
-
-
排队规则 (QDisc - Traffic Control)
-
这是常见的丢包点。数据包进入网卡驱动前,会先进入 QDisc (Queueing Discipline) 队列(如
pfifo_fast,fq_codel)。 -
如果发送速度太快,超过了网卡的处理能力,或者配置了流量整形,包可能会在这里被丢弃或延迟。
-
-
设备驱动层 (Driver Layer)
-
驱动程序将
sk_buff映射到 DMA (Direct Memory Access) 区域。 -
驱动将数据包的描述符(Descriptor,包含物理地址和长度)放入 TX Ring Buffer(发送环形缓冲区)。这是一个位于内核内存中的 FIFO 队列,网卡和 CPU 共享。
-
驱动通知网卡:“有新数据要发!”(通常是通过写寄存器触发)。
-
-
硬件网卡 (NIC)
-
网卡通过 DMA 直接从主存(RAM)中读取数据,无需 CPU 参与。
-
网卡将数据序列化,加上帧头帧尾(MAC层),通过物理线路(PHY)发送出去。
-
完成中断: 发送完成后,网卡触发一个硬中断,告诉 CPU发完了
-
二、 数据接收流程 (RX: Receive)
从 网卡收到光/电信号 到 应用层 read()
这是一个“解包”的过程,是性能瓶颈高发区。
-
硬件网卡 (NIC)
-
光/电信号到达,PHY 芯片解调。
-
网卡检查 MAC 地址(如果开启混杂模式则不检查)。
-
DMA 写入: 网卡通过 DMA 将数据包直接写入预先分配好的 RX Ring Buffer (在 RAM 中)。
-
触发硬中断: 网卡发起一个硬中断 (Hard IRQ),告诉 CPU受到包了
-
-
硬中断处理 (Hard IRQ)
-
CPU 停止当前工作,执行中断处理程序。
-
关键点: 为了避免 CPU 被频繁中断卡死,硬中断处理程序只做极少的事:
-
屏蔽该网卡的中断(防止后续包打断处理)。
-
触发 软中断 (SoftIRQ)(
NET_RX_SOFTIRQ)。
-
-
硬中断结束,CPU 回到之前的上下文,但调度器知道有一个软中断待处理。
-
-
软中断与 NAPI (SoftIRQ & ksoftirqd)
-
这是 Linux 网络处理的核心。
-
ksoftirqd进程(或者在系统调用返回前)开始轮询(NAPI Polling)网卡。 -
NAPI: 此时不再通过中断通知,而是 CPU 主动去 RX Ring Buffer 里“捞”数据。这样在高流量下能避免“中断风暴”。
-
-
设备驱动层
-
驱动程序将 RX Ring Buffer 中的数据映射回内核可读的格式。
-
封装成
sk_buff。 -
去除以太网帧头。
-
-
协议栈处理 (IP -> TCP/UDP)
-
网络层: IP 校验、路由判断(是给我的吗?)、Netfilter (
PREROUTING,INPUT链)。 -
传输层: 查找对应的 Socket(通过 IP + 端口)。
-
数据被放入该 Socket 的 接收缓冲区 (Recv Buffer)。
-
-
应用层 (User Space)
-
应用程序调用
read()或recv()。 -
上下文切换: 如果 Socket 缓冲区为空,应用阻塞(或 epoll 等待)。
-
内存拷贝: 数据从内核的 Socket 缓冲区 拷贝 到用户态的应用缓冲区。
-
应用处理数据。
-
三、 流程中的关键概念与故障点
流程中对应的故障点:
| 阶段 | 关键组件/动作 | 潜在故障/丢包原因 | 排查工具 |
|---|---|---|---|
| L1 物理 | NIC -> DMA | Ring Buffer 溢出 (CPU 没来得及收,网卡没地儿写) | ethtool -S eth0 | grep drop/overrun |
| L2 驱动 | NAPI / SoftIRQ | CPU 单核软中断 100%,来不及处理包 | top (看 si), mpstat -P ALL |
| L3 IP | Netfilter/Conntrack | 防火墙规则 DROP,或 conntrack 表满 |
dmesg, iptables -nvL |
| TX 队列 | QDisc | 发送速率超过网卡物理带宽 | tc -s qdisc show |
| L4 Socket | Socket Buffer | 应用处理太慢,Socket 接收缓冲区满 (Recv-Q 满) |
ss -nmp, netstat |
| L7 应用 | User Copy | 应用卡死、GC 停顿 (Go)、锁竞争 | pprof, strace |
关注丢包
这个流程中最需要关注的是 RX Ring Buffer (网卡层面丢包) 和 Socket Recv Buffer (Go 程序处理慢导致丢包)。