2. 网络收包路径全景:从网卡到应用的数据流
理解网络收包的全路径,是配置软中断CPU亲和性的前提。数据从物理网线到达用户态应用,依次经过DMA传输、硬中断、软中断和协议栈处理四个核心阶段。每个阶段都涉及特定的CPU资源消耗,只有厘清这些环节,才能精准定位亲和性优化的切入点。
核心认知: 收包路径中,硬中断和软中断是CPU开销最集中的两个阶段。硬中断负责快速通知,软中断负责实际的数据搬运与协议解析。将软中断绑定到特定CPU核心,可以显著提升缓存命中率并减少锁竞争。
2.1 收包路径四阶段总览
下图展示了从网卡到应用的数据流全景,包含DMA、硬中断、软中断和协议栈四个关键环节:
2.2 阶段一:DMA与Ring Buffer
当数据帧到达网卡时,网卡通过DMA(Direct Memory Access)直接将数据写入主内存中的Ring Buffer。此过程完全由硬件完成,不占用CPU周期。
| 组件 | 作用 | CPU参与 |
|---|---|---|
| 网卡硬件 | 接收数据帧,解析MAC地址 | 否 |
| DMA引擎 | 将数据从网卡FIFO搬运到内存 | 否 |
| Ring Buffer | 环形队列,存储sk_buff指针 | 否(分配时需CPU) |
性能提示: Ring Buffer的大小直接影响丢包率。过小会导致DMA写入失败(丢包),过大会增加内存占用。建议根据网卡型号和流量特征调整
ethtool -g eth0 查看当前配置。
2.3 阶段二:硬中断(Hard IRQ)
DMA完成后,网卡通过PCIe总线向CPU发送硬中断信号。CPU收到中断后,立即暂停当前任务,执行中断处理程序(ISR)。
硬中断处理程序执行最小必要操作:
- 确认中断来源(哪个网卡、哪个队列)
- 禁止该中断线(防止嵌套)
- 标记软中断需要处理(
raise_softirq) - 唤醒
ksoftirqd内核线程
注意: 硬中断处理时间过长会阻塞其他中断,导致系统响应延迟。因此ISR必须快速返回,将繁重工作交给软中断。这也是NAPI(New API)的设计初衷——在流量高时合并中断,减少硬中断次数。
2.4 阶段三:软中断(SoftIRQ)
硬中断处理程序唤醒ksoftirqd内核线程后,软中断开始执行真正的数据包处理。软中断运行在软件中断上下文,可以响应其他硬中断,但不能睡眠。
软中断处理的核心任务:
- 从Ring Buffer取出
sk_buff(socket buffer) - 调用网卡驱动的
poll方法批量处理数据包 - 将数据包送入协议栈(IP层、TCP/UDP层)
- 将处理后的数据放入socket接收队列
| 软中断类型 | 向量号 | 处理函数 | 主要工作 |
|---|---|---|---|
| NET_TX_SOFTIRQ | 1 | net_tx_action | 发送数据包 |
| NET_RX_SOFTIRQ | 0 | net_rx_action | 接收数据包(本课程重点) |
2.5 阶段四:协议栈处理
协议栈处理在软中断上下文中完成,不额外调度。主要步骤:
- 链路层: 检查MAC地址、VLAN标签,剥离以太网帧头
- 网络层: 检查IP头部,路由查找,处理分片重组
- 传输层: TCP/UDP头部校验,查找socket,处理流量控制
- 数据交付: 将数据放入socket的接收缓冲区,唤醒等待的应用进程
性能关键: 协议栈处理是CPU密集型的操作。每个数据包都需要进行多次校验、查找和拷贝。将软中断绑定到具有独立L2缓存的CPU核心,可以显著减少缓存未命中,提升吞吐量。实测表明,合理的亲和性配置可使收包性能提升20%~40%。
2.6 各阶段CPU资源消耗对比
| 阶段 | CPU占用 | 执行上下文 | 可抢占 | 优化手段 |
|---|---|---|---|---|
| DMA | 0% | 硬件 | — | 增大Ring Buffer |
| 硬中断 | < 5% | 中断上下文 | 否 | IRQ Affinity |
| 软中断 | 60%~80% | 软中断上下文 | 否 | CPU亲和性(本课程核心) |
| 协议栈 | 15%~30% | 软中断上下文 | 否 | GRO/LRO、RPS |
| 应用读取 | 5%~10% | 进程上下文 | 是 | epoll、零拷贝 |
2.7 关键数据结构:sk_buff
sk_buff 是网络收包路径中最重要的数据结构,贯穿DMA到协议栈的整个过程。理解其结构有助于优化内存访问:
struct sk_buff {
/* 链表指针 */
struct sk_buff *next;
struct sk_buff *prev;
/* 协议层指针 */
struct net_device *dev;
unsigned int len; // 数据总长度
unsigned int data_len; // 数据段长度
__u16 protocol; // 协议类型(ETH_P_IP等)
/* 头部指针 */
sk_buff_data_t transport_header; // 传输层头部
sk_buff_data_t network_header; // 网络层头部
sk_buff_data_t mac_header; // 链路层头部
/* 数据指针 */
unsigned char *head; // 缓冲区起始
unsigned char *data; // 数据起始
unsigned char *tail; // 数据结束
unsigned char *end; // 缓冲区结束
/* 其他字段 */
struct sock *sk; // 关联的socket
atomic_t users; // 引用计数
};
优化提示: sk_buff的分配和释放是收包路径中的高频操作。使用
slab分配器预分配sk_buff缓存,可以减少内存分配开销。在亲和性配置中,确保软中断所在CPU的sk_buff缓存热,可减少跨CPU内存访问。
2.8 本章小结
网络收包路径是一个从硬件到软件的完整流水线:
- DMA 负责数据搬运,不消耗CPU
- 硬中断 快速通知CPU,执行最小操作
- 软中断 承担主要处理工作,是性能优化的核心
- 协议栈 在软中断上下文中完成数据解析与交付