Sirius
Sirius

目录

Linux 网络数据包流转

整个收发包流程的核心在于数据如何在 用户态 (User Space)内核态 (Kernel Space)硬件 (Hardware) 之间流转。

发送 (TX)接收 (RX) 流程如下:


从应用层 write() 到 网卡发送

  1. 应用层 (User Space)

    • 应用程序(例如你的 Go 程序)调用系统调用,如 write(fd, buffer)sendto()

    • 关键动作内存拷贝。数据从用户态内存(User Buffer)被拷贝到内核态内存(Kernel Memory),此时数据被封装成 Linux 网络核心数据结构 —— sk_buff (Socket Buffer)。

  2. 传输层 (Transport Layer - TCP/UDP)

    • 内核根据 Socket 类型(TCP/UDP)处理数据。

    • TCP: 分段 (Segmentation)、计算校验和、处理序列号、滑动窗口。

    • UDP: 直接添加 UDP 头。

    • 此时 sk_buff 增加了 L4 头部。

  3. 网络层 (Network Layer - IP)

    • 查找路由表:决定数据包去往哪里(下一跳网关或本机)。

    • Netfilter (iptables/nftables): 经过 OUTPUT 链,防火墙规则在这里生效。

    • IP 头部被添加。如果有必要,进行 IP 分片。

  4. 排队规则 (QDisc - Traffic Control)

    • 这是常见的丢包点。数据包进入网卡驱动前,会先进入 QDisc (Queueing Discipline) 队列(如 pfifo_fast, fq_codel)。

    • 如果发送速度太快,超过了网卡的处理能力,或者配置了流量整形,包可能会在这里被丢弃或延迟。

  5. 设备驱动层 (Driver Layer)

    • 驱动程序将 sk_buff 映射到 DMA (Direct Memory Access) 区域。

    • 驱动将数据包的描述符(Descriptor,包含物理地址和长度)放入 TX Ring Buffer(发送环形缓冲区)。这是一个位于内核内存中的 FIFO 队列,网卡和 CPU 共享。

    • 驱动通知网卡:“有新数据要发!”(通常是通过写寄存器触发)。

  6. 硬件网卡 (NIC)

    • 网卡通过 DMA 直接从主存(RAM)中读取数据,无需 CPU 参与。

    • 网卡将数据序列化,加上帧头帧尾(MAC层),通过物理线路(PHY)发送出去。

    • 完成中断: 发送完成后,网卡触发一个硬中断,告诉 CPU发完了


从 网卡收到光/电信号 到 应用层 read()

这是一个“解包”的过程,是性能瓶颈高发区。

  1. 硬件网卡 (NIC)

    • 光/电信号到达,PHY 芯片解调。

    • 网卡检查 MAC 地址(如果开启混杂模式则不检查)。

    • DMA 写入: 网卡通过 DMA 将数据包直接写入预先分配好的 RX Ring Buffer (在 RAM 中)。

    • 触发硬中断: 网卡发起一个硬中断 (Hard IRQ),告诉 CPU受到包了

  2. 硬中断处理 (Hard IRQ)

    • CPU 停止当前工作,执行中断处理程序。

    • 关键点: 为了避免 CPU 被频繁中断卡死,硬中断处理程序只做极少的事:

      1. 屏蔽该网卡的中断(防止后续包打断处理)。

      2. 触发 软中断 (SoftIRQ)NET_RX_SOFTIRQ)。

    • 硬中断结束,CPU 回到之前的上下文,但调度器知道有一个软中断待处理。

  3. 软中断与 NAPI (SoftIRQ & ksoftirqd)

    • 这是 Linux 网络处理的核心。

    • ksoftirqd 进程(或者在系统调用返回前)开始轮询(NAPI Polling)网卡。

    • NAPI: 此时不再通过中断通知,而是 CPU 主动去 RX Ring Buffer 里“捞”数据。这样在高流量下能避免“中断风暴”。

  4. 设备驱动层

    • 驱动程序将 RX Ring Buffer 中的数据映射回内核可读的格式。

    • 封装成 sk_buff

    • 去除以太网帧头。

  5. 协议栈处理 (IP -> TCP/UDP)

    • 网络层: IP 校验、路由判断(是给我的吗?)、Netfilter (PREROUTING, INPUT 链)。

    • 传输层: 查找对应的 Socket(通过 IP + 端口)。

    • 数据被放入该 Socket 的 接收缓冲区 (Recv Buffer)

  6. 应用层 (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 程序处理慢导致丢包)。