如果你问我 Linux 运维和后台开发里最值得花时间啃的模块,我的答案不是某条命令,而是网络层。早几年我也觉得网络层就是 IP 地址和路由表,能 ping 通就能上班。直到被线上故障教育了几轮,才明白数据从网卡进来,经过驱动、协议栈、Socket,最后到应用进程,每一站都有计数器、队列和超时时间,任何一环出问题,业务表现都会变得很奇怪。这篇文章不打算背 OSI 七层模型,而是结合我真实踩过的坑,把 Linux 网络层从收包到排查、从 TCP 参数到容器网络的核心逻辑完整走一遍,适合刚开始学 Linux 网络的人,也适合想补齐网络排查能力的运维和 SRE。
1. 收包链路:数据包从网卡到应用进程的每一站
1.1 从硬中断到软中断:网卡收包不是“内核顺便处理”的
很多初学者以为数据到了网卡,内核就会自然而然地处理它。实际上,一条数据包从物理网线进入应用进程,首先要过的就是驱动和中断这道关。网卡通过 DMA 把数据直接写入内存里的环形缓冲区(Ring Buffer),此时 CPU 还没有参与拷贝,这个设计是为了避免大量数据在网卡和内核之间来回复制。写入完成后,网卡发起硬件中断,通知 CPU 有数据到达。硬件中断处理程序必须非常短,因为它会打断正在运行的进程,如果长时间占用 CPU,系统整体延迟会明显升高。
真正处理数据包的逻辑被放到了软中断里,也就是 Linux 的 NET_RX_SOFTIRQ。这里有一个关键机制叫 NAPI。在高流量场景下,网卡会先关闭硬中断,驱动反复从 Ring Buffer 里取包交给协议栈,直到取完或者达到预算,再重新开启硬中断。这种“批量收包”的思路,避免了每个数据包都触发一次硬中断,极大降低了 CPU 开销。另一个容易忽略的点是中断亲和性。默认情况下,网卡中断可能全部落在 CPU0 上,导致 CPU0 的软中断使用率飙升,其他核心却很空闲。可以用 /proc/interrupts 查看各 CPU 上的中断分布,也可以配置 IRQ affinity 或开启 RPS(Receive Packet Steering)来让软中断分摊到多个 CPU。
判断软中断是否在丢包,最直接的文件是 /proc/net/softnet_stat。这个文件每行对应一个 CPU,列的含义在不同内核版本上略有差异,但第二列和第三列分别和 CPU 收到的包数量、由于队列溢出导致丢包的数据包数量有关。如果第三列持续增长,说明你的网络包在进入协议栈之前就已经被丢弃了。很多人排查丢包只盯着应用日志,却不知道问题早在这一层就发生了。
1.2 sk_buff:Linux 网络层最重要的数据结构
在 Linux 内核里,网络数据包不会赤裸裸地在各层之间传递,而是被包装在一个叫 sk_buff(Socket Buffer)的结构体里。这个结构体里保存着数据包在各个协议层的头部指针,比如 mac_header、network_header、transport_header,还有数据包长度、mark 标记、时间戳等信息。为什么需要这样一个结构?因为数据包在链路层要加以太网头,在网络层要处理 IP 头,在传输层要解析 TCP/UDP 头。如果只用一块原始内存从头到尾处理,每层都要移动指针,还要频繁拷贝,效率太低了。
sk_buff 的设计允许每个协议层只操作自己关注的那一段,通过调整指针就能完成包头封装和解析。同时它支持 headroom 预留,也就是在数据区前面预留出一块空间,当数据包向上一层传递时,不需要把整包数据往后挪,只需要在头部插入新的协议头。这个机制也成了后来零拷贝技术的基础之一。我们平时写应用不会直接跟 sk_buff 打交道,但理解它有助于看懂协议栈行为。比如抓包时发现 TCP 分段异常,很多时候不是 sk_buff 的问题,而是 MTU 和 IP 分片的问题。
内核在分配 sk_buff 时使用的是专门的内存缓存,如果系统内存碎片化严重,分配失败,网络吞吐就会突然下降。这类问题在长时间运行的服务器上并不少见,尤其是在频繁创建和销毁连接的场景下。如果你看到 dmesg 里有类似 kvmalloc 失败或者网络吞吐抖动,却又找不到应用层原因,往内存碎片和 sk_buff 缓存分配的方向查,往往会有收获。
1.3 数据包可能在哪几个点位丢失
一条数据包从网卡到应用进程,有四个最容易丢包的卡口。
第一个是网卡 Ring Buffer。当流量瞬间暴涨,数据包到达速率超过内核处理速度,Ring Buffer 被占满,新到的数据包无处存放,网卡会直接丢弃。查看 ethtool -S eth0 时,rx_missed、rx_dropped 这些计数器增长就能看到。第二个是软中断处理不过来。数据包进入软中断队列后,如果 CPU 处理速度跟不上,队列溢出,/proc/net/softnet_stat 会记录丢包。第三个是 IP 层和 netfilter 层。路由查找失败、iptables 规则做了 DROP、IP 分片重组超限,都可能导致丢包。第四个是 TCP 层和 Socket 层。Socket 接收缓冲区满,或者 listen 的 accept 队列满,应用没有及时取走数据,内核只能丢弃或触发重传。
把这四个卡口记在脑子里,排查问题时就不是笼统地看“是否丢包”,而是会问自己“丢在哪个环节”。不同的丢包位置,对应的计数器和命令完全不同。这也是网络层排错和纯应用层排错最大的区别:应用层只需要看日志,网络层需要把整条流水线串起来看。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
