3. Linux 网络协议栈基础:数据包收发、Socket缓冲区与NAPI
这一章,我们聊聊Linux网络协议栈最核心的几个机制。说白了,就是数据包从网线进来,到应用程序读到它,这中间到底发生了什么。反过来,应用发数据,又是怎么一步步送到网卡上的。
我在RK平台上调网络性能时,发现很多人一上来就调中断、调队列,但连NAPI和传统中断的区别都说不清楚。嗯,这样调优很容易跑偏。我们先打好基础。
3.1 网络数据包收发全流程
先看一张整体流程图,把数据包的“一生”串起来。
接收方向,我习惯把它分成六个阶段。每个阶段都有坑,我们一个一个说。
3.1.1 从网线到Ring Buffer
数据包从网线进来,网卡通过DMA直接把数据写到内存里的Ring Buffer。这个过程CPU完全不参与。你想想看,如果每个包都要CPU去搬,那千兆网卡早就把CPU撑爆了。
ethtool -g eth0 查看Ring Buffer大小。默认值往往偏小,大流量下容易丢包。我一般会调大Rx队列深度。
3.1.2 硬中断与NAPI的配合
DMA写完数据后,网卡触发硬中断。注意,硬中断里只做一件事:告诉内核“有数据来了”,然后立刻返回。真正的收包工作,交给NAPI在软中断中轮询完成。
为什么要这么设计?因为硬中断优先级太高,如果每个包都触发硬中断,系统会陷入“中断风暴”,也就是活锁——CPU光顾着响应中断,没时间处理数据了。
irqbalance 或者手动绑中断亲和性才解决。
3.2 Socket缓冲区管理
数据包经过协议栈处理后,最终要放到Socket的接收缓冲区里,等待应用程序来读。这个缓冲区,就是 sk_buff 结构体。
sk_buff 是Linux网络子系统中最重要的数据结构,没有之一。它贯穿整个协议栈,从驱动层到应用层,所有操作都围绕着它。
3.2.1 sk_buff 的核心字段
| 字段 | 作用 | 我的备注 |
|---|---|---|
dev |
接收/发送的网络设备 | 多网卡时靠它区分 |
data / head |
数据指针范围 | head是起始,data是当前协议头 |
len |
数据长度 | 不包括协议头预留空间 |
cb[] |
控制块,各层私有数据 | TCP重传、IP分片都用它 |
sk |
所属Socket | 没有这个,数据就找不到应用 |
我个人习惯在调试时,用 perf probe 或者 ftrace 跟踪 __alloc_skb 和 kfree_skb,看看谁在频繁分配和释放sk_buff。如果分配次数异常高,说明可能有内存泄漏或者小包攻击。
3.2.2 缓冲区大小与调优
Socket接收缓冲区的大小,直接影响吞吐量。太小了,数据包会被丢弃;太大了,浪费内存,而且延迟会变高。
查看当前值:
# 查看接收缓冲区大小(字节)
cat /proc/sys/net/ipv4/tcp_rmem
# 输出示例:4096 131072 6291456
# 分别对应:最小值、默认值、最大值
在RK平台上,如果跑视频流或大数据传输,我建议把默认值调大:
echo '4096 262144 8388608' > /proc/sys/net/ipv4/tcp_rmem
3.3 NAPI与新API机制
NAPI(New API)是Linux网络子系统中里程碑式的改进。它解决了传统中断方式在高吞吐下的性能瓶颈。
3.3.1 传统中断 vs NAPI
| 特性 | 传统中断方式 | NAPI方式 |
|---|---|---|
| 触发方式 | 每个包都触发硬中断 | 第一个包触发中断,后续轮询 |
| 高吞吐表现 | 中断风暴,CPU满载 | 轮询模式,CPU利用率平稳 |
| 低延迟场景 | 响应快,适合低流量 | 轮询有微秒级延迟 |
| 适用场景 | 低速网络(< 1Gbps) | 高速网络(≥ 1Gbps) |
说白了,NAPI的核心思想就是:中断来了,先关中断,然后轮询收包,直到收完或者达到配额,再开中断。这样避免了每个包都打断CPU。
3.3.2 NAPI的工作流程
- 网卡收到第一个包,触发硬中断。
- 中断处理程序调用
napi_schedule(),把NAPI结构体加入软中断队列。 - 软中断(NET_RX_SOFTIRQ)执行
napi_poll()。 - 轮询函数从Ring Buffer取包,送入协议栈。
- 如果收完所有包,或者达到
budget(默认64个包),退出轮询,重新开启硬中断。
/proc/sys/net/core/netdev_budget 控制每次NAPI轮询最多处理多少个包。默认300(较新内核),在RK平台上如果CPU核数少,可以适当降低到128,防止软中断占用太多CPU时间。
3.3.3 多队列与RPS/RFS
现代网卡支持多队列(RSS),每个队列可以绑定到不同CPU。但RK平台有些网卡(比如内置的GMAC)不支持硬件RSS。这时候可以用软件的RPS(Receive Packet Steering)来模拟。
开启RPS:
# 假设有4个CPU,把eth0的队列0分发到CPU0-3
echo 'f' > /sys/class/net/eth0/queues/rx-0/rps_cpus
RFS(Receive Flow Steering)更进一步,它根据流的哈希值,把同一个流的数据包送到同一个CPU,提高缓存命中率。我建议在数据库或视频流场景下开启RFS。
3.4 本章小结
这一章我们覆盖了三个核心知识点:数据包从网卡到应用的完整路径、sk_buff缓冲区的管理、以及NAPI如何拯救高吞吐场景。这些是后续调优的基础,也是排查网络问题的第一站。
记住一句话:网络性能调优,本质上是平衡中断、轮询、内存和CPU之间的关系。没有银弹,只有理解原理后的针对性调整。