4. NAPI 机制原理:轮询与中断结合、budget 与 weight 参数、NAPI 调度流程
NAPI(New API)是 Linux 内核为解决传统中断驱动网络在高吞吐场景下产生的 中断风暴(Interrupt Storm) 而设计的混合接收机制。其核心思想是:中断驱动唤醒,轮询批量收包。本章深入剖析 NAPI 的轮询与中断结合原理、关键参数 budget 与 weight 的含义,以及完整的调度流程。
4.1 轮询与中断结合的设计动机
传统网络驱动在每次数据包到达时都会触发硬件中断,CPU 立即进入中断上下文处理。当网络流量极大时,频繁的中断会导致 CPU 被中断上下文完全占据,无法执行用户态进程,系统吞吐量急剧下降。NAPI 通过以下方式解决该问题:
- 中断触发阶段:第一个数据包到达时,硬件仍产生中断,驱动在中断处理函数中关闭该设备的硬件中断,并将设备的 NAPI 结构体加入轮询列表。
- 轮询收包阶段:内核在软中断上下文(
ksoftirqd或NET_RX_SOFTIRQ)中调用驱动的poll回调函数,批量从硬件接收队列中取出数据包,直到预算耗尽或队列为空。 - 重新开启中断:当轮询结束且队列为空时,重新开启硬件中断,等待下一批数据包。
关键优势: 高流量时避免中断风暴,低流量时保持低延迟。NAPI 将中断处理从“每个包一次中断”优化为“每批包一次中断+轮询”。
4.2 budget 与 weight 参数详解
NAPI 轮询过程受两个关键参数控制:budget 和 weight。它们共同决定了单次轮询的收包上限和 CPU 时间分配。
| 参数 | 作用域 | 默认值 | 含义 |
|---|---|---|---|
| budget | 全局(每个 CPU) | 300 | 单次软中断处理中,所有 NAPI 实例总共最多处理的包数。防止单个 CPU 在软中断中耗时过长。 |
| weight | 每个 NAPI 实例 | 64 | 单个 NAPI 实例在单次 poll 调用中最多处理的包数。通常与驱动接收队列深度相关。 |
实践建议: 对于万兆网卡,建议将
weight 提升至 128~256,同时适当增大 budget 至 600~1000,以匹配高吞吐场景。但需注意 budget 过大会增加软中断延迟,影响实时性。
4.2.1 参数协同工作示例
假设系统有 2 个 NAPI 实例(对应 2 个 RX 队列),budget=300,weight=64:
- 软中断开始,剩余预算 = 300。
- 调度 NAPI 实例 1:
poll最多处理 64 个包,实际处理 50 个,剩余预算 = 250。 - 调度 NAPI 实例 2:
poll最多处理 64 个包,实际处理 64 个,剩余预算 = 186。 - 实例 1 再次被调度:
poll处理 40 个包,剩余预算 = 146。 - 实例 2 再次被调度:
poll处理 60 个包,剩余预算 = 86。 - 重复直到剩余预算为 0 或所有实例队列为空。
注意: 如果
weight 设置过大,单个 NAPI 实例可能独占预算,导致其他实例(如其他网卡或队列)被饿死。合理配置应保证 weight ≤ budget / NAPI 实例数。
4.3 NAPI 调度流程
NAPI 的完整调度流程涉及硬件中断、软中断、内核线程 ksoftirqd 以及驱动 poll 回调。以下是标准流程:
| 步骤 | 上下文 | 操作 |
|---|---|---|
| 1 | 硬件中断 | 网卡收到数据包,触发 IRQ。CPU 执行驱动注册的中断处理函数 irq_handler。 |
| 2 | 硬件中断 | 驱动调用 napi_schedule,将 NAPI 结构体加入当前 CPU 的 softnet_data->poll_list,并触发 NET_RX_SOFTIRQ 软中断。 |
| 3 | 软中断 | net_rx_action 函数执行,遍历 poll_list,依次调用每个 NAPI 实例的 poll 回调。 |
| 4 | 软中断 | poll 回调从硬件接收描述符中批量获取数据包,构造 sk_buff,调用 napi_gro_receive 或 netif_receive_skb 送入协议栈。 |
| 5 | 软中断 | 如果 poll 处理完所有包(队列空),调用 napi_complete_done 从 poll_list 移除,并重新开启硬件中断。 |
| 6 | 软中断 | 如果 poll 未处理完(预算耗尽),NAPI 实例保留在 poll_list 中,等待下一次软中断或 ksoftirqd 继续调度。 |
核心数据结构:
struct napi_struct 包含 poll_list 链表节点、poll 函数指针、weight、state(标记是否被调度)等字段。每个设备或 RX 队列对应一个 NAPI 实例。
4.4 NAPI 与 ksoftirqd 的协作
当软中断执行时间过长或频率过高时,内核会将软中断处理委托给 ksoftirqd 内核线程。NAPI 与此机制紧密配合:
- 直接软中断路径:在中断返回时,如果软中断未处理完且执行时间较短,直接在
irq_exit中处理。 - ksoftirqd 路径:如果软中断执行时间超过
2 * HZ / 1000(约 2 个 jiffies),或软中断被重新触发,则唤醒ksoftirqd线程在进程上下文继续轮询。 - NAPI 的适应性:NAPI 的
poll回调在软中断或ksoftirqd中执行,对驱动透明。驱动只需实现poll函数,无需关心调度上下文。
性能调优点: 通过调整
/proc/sys/net/core/budget 和 /proc/sys/net/core/netdev_budget_usecs(软中断时间预算),可以控制 NAPI 轮询的 CPU 占用。对于纯轮询模式(如 DPDK),可关闭 NAPI 中断,完全使用用户态轮询。
4.5 本章小结
- NAPI 通过 中断唤醒 + 轮询收包 解决了高吞吐下的中断风暴问题。
- budget 控制全局收包上限,weight 控制单个 NAPI 实例的单次收包上限,两者需协同配置。
- NAPI 调度流程从硬件中断开始,经过
napi_schedule、软中断net_rx_action、驱动poll,最终通过napi_complete_done重新开启中断。 - 理解 NAPI 原理是配置中断亲和性、优化多队列网卡性能的基础。