1. NAPI 概述:背景与动机

1.1 传统中断方式的瓶颈

在早期 Linux 网络设备驱动中,每当一个数据包到达网卡(NIC),硬件会触发一个 IRQ(中断请求),CPU 立即暂停当前任务,保存上下文,执行中断服务程序(ISR)将数据包从硬件 FIFO 拷贝到内核协议栈。这种“每包必中断”的模式在低速网络(10/100 Mbps)下表现良好,但当链路速率提升到 1 Gbps 甚至 10 Gbps 时,中断风暴成为性能杀手。

瓶颈维度 传统中断方式表现 后果
中断频率 每包触发一次 IRQ,10 Gbps 下可达 14.88 Mpps(64 字节包) CPU 被中断淹没,用户态进程几乎得不到时间片
上下文切换开销 每次中断需保存/恢复寄存器、TLB、Cache 状态 Cache 污染严重,有效吞吐量下降 30%~50%
中断亲和性 中断可能在不同 CPU 间漂移 Cache 局部性被破坏,跨 NUMA 访问延迟增加
活锁(Livelock) 高负载下 CPU 100% 忙于处理中断,无法处理上层协议 系统吞吐量归零,网络看似“死机”
⚠ 活锁场景: 当网络负载超过 CPU 的中断处理能力时,每个新中断都会打断正在处理上一个包的中断下半部(softirq),导致没有任何一个包能被完整送入协议栈。系统看起来“忙碌”,但实际吞吐量为 0。
传统中断方式 vs NAPI 轮询方式对比 传统中断方式 包到达 触发 IRQ ISR 处理 拷贝数据 每包一次中断 → 高负载下活锁 NAPI 轮询方式 包到达 首次 IRQ 关闭 IRQ 轮询收包 批量收包 → 无中断风暴

1.2 NAPI 的核心思想:中断 + 轮询混合

NAPI(New API)由 Alexey Kuznetsov 在 Linux 2.4.20 中引入,核心思想是:用一次中断唤醒轮询,用轮询批量处理后续数据包。它打破了“每包必中断”的僵化模式,将中断处理与轮询处理有机结合。

🔑 核心公式: NAPI 吞吐量 ≈ 中断延迟 + (批量大小 × 单包处理时间) / 批量大小。当批量足够大时,中断开销被均摊到每个包上趋近于 0。

1.3 NAPI 的工作流程

  1. 中断触发: 网卡收到第一个数据包,硬件产生 IRQ,驱动 ISR 被调用。
  2. 关闭中断: ISR 立即关闭该网卡接收中断,并将设备注册到 CPU 的 softirq 轮询列表(poll_list)中。
  3. 调度 softirq: 触发 NET_RX_SOFTIRQ,内核在 softirq 上下文中调用驱动注册的 poll 回调函数。
  4. 轮询收包: poll 函数从硬件 FIFO 或 DMA ring 中批量读取数据包,每次最多处理 budget 个包(默认 64)。
  5. 退出条件:
    • 如果包被取完(FIFO 空),则从 poll_list 移除,重新开启中断。
    • 如果达到 budget 但仍有包,则保留在 poll_list 中,等待下一次 softirq 继续轮询。
NAPI 中断+轮询混合工作流程 ① 网卡收到数据包 ② 硬件触发 IRQ ③ ISR 关闭中断 ④ 注册到 poll_list ⑤ poll() 批量收包 仍有包且未超 budget ⑥ 包取完 → 开中断

1.4 NAPI 的关键数据结构

数据结构 所在文件 作用
struct napi_struct include/linux/netdevice.h NAPI 实例,包含 poll 回调、设备指针、状态标志、poll_list 节点
poll_list softirq 上下文 每个 CPU 维护一个链表,挂载所有待轮询的 napi_struct
budget net/core/dev.c 每次 softirq 允许处理的最大包数(默认 300,每个设备最多 64)
NAPI_STATE_SCHED include/linux/netdevice.h 标志位,表示该 NAPI 实例已被调度到 poll_list 中
💡 设计哲学: NAPI 将“中断频率”与“数据包到达率”解耦。无论网络负载多高,中断频率上限被限制在 HZ / budget 量级(例如 1000 Hz / 64 ≈ 15.6 KHz),远低于 14.88 Mpps 的包到达率。

1.5 NAPI 的适用场景

  • 高速网络(≥1 Gbps): 批量收包优势明显,吞吐量提升 2~5 倍。
  • 低延迟场景: 首次包仍由中断触发,延迟与纯中断方式相当。
  • 虚拟化环境: vhost-net、virtio 等虚拟网卡驱动广泛使用 NAPI。
  • 不适用场景: 极低负载(如 IoT 设备,每秒几包),轮询反而浪费 CPU。
NAPI 适用性决策树 网络负载评估 高负载(≥1 Gbps) ✅ 推荐使用 NAPI 低负载(IoT/几包每秒) ❌ 纯中断更优

1.6 NAPI 的演进历史

内核版本 里程碑
2.4.20 Alexey Kuznetsov 首次提交 NAPI 补丁
2.6.0 NAPI 成为默认网络收包路径
2.6.24 支持多队列网卡的 RPS(Receive Packet Steering)
3.8 引入 busy poll 机制,进一步降低轮询延迟
4.5 NAPI 支持 GRO(Generic Receive Offload),批量合并小包
5.0+ XDP(eXpress Data Path)与 NAPI 协同工作
📊 性能数据: 在 10 Gbps 测试中,传统中断方式在 64 字节小包下吞吐量仅 1.2 Gbps(CPU 100%),而 NAPI 轮询方式可达 9.8 Gbps(CPU 占用约 60%)。NAPI 将中断开销从每包 ~2 μs 降低到每批 ~0.1 μs。