Linux NAPI机制知识点总结

https://docs.kernel.org/networking/napi.html 的总结

NAPI (New API,但现已无特定含义) 是 Linux 内核中用于高效处理网络数据包的一种机制,旨在减少高负载下的中断开销。

其​目的​​是为了解决传统基于中断的包处理在高流量下的性能问题(“中断活锁”)。通过混合​​中断​​和​​轮询​​模式,在低负载时使用中断保证低延迟,在高负载时切换到轮询保证高吞吐量。

设备通过中断通知有新数据包 -> 内核调度对应的 NAPI 实例 -> 在软中断上下文(或内核线程)中轮询处理多个数据包 -> 处理完毕后再打开中断。

核心数据结构与 API

  1. struct napi_struct​:

    • 核心数据结构,代表一个 NAPI 实例,保存其状态信息。
    • 通常与一个中断或一个队列(RX/TX)相关联。
  2. ​控制 API (初始化和状态管理)​​:

    • netif_napi_add() / netif_napi_del(): 向系统添加/删除一个 NAPI 实例(通常附加到网络设备上)。
    • napi_enable() / napi_disable(): 启用/禁用 NAPI 实例。禁用状态下实例不会被调度,poll 方法不会被调用。​​注意​​:API 非幂等,错误调用顺序可能导致死锁或竞态。
  3. ​数据路径 API (调度与处理)​​:

    • napi_schedule(): ​​核心调度函数​​。通常在设备的中断处理程序中调用,通知内核有数据需要处理,并获取 NAPI 实例的所有权。
    • napi_schedule_irqoff(): napi_schedule() 的变体,用于已知在中断上下文中调用的情况,可优化中断屏蔽操作。
    • napi_complete_done(): ​​完成处理函数​​。当驱动程序的 poll 方法处理完所有事件后调用此函数,释放实例的所有权。​​警告​​:budget 为 0 时绝不能调用;若处理恰好用完 budget 且工作已完成,需谨慎返回 budget - 1 或等待下次调用。

驱动程序实现要点

  1. poll 方法​​:

    • 驱动必须实现的回调函数,由内核调度执行实际的数据包处理工作。
    • ​参数 budget​:限制一次 poll 调用最多可处理的​​接收​​(RX)数据包数量(发送 TX 处理无此限制)。若返回值为 budget,表示还有工作未完成,内核会再次调度;若小于 budget,表示本轮处理已完成。
  2. ​中断屏蔽策略​​:

    • 调度 NAPI 后应​​屏蔽设备中断​​,防止不必要的重复中断,直到 napi_complete_done() 被调用后再解除屏蔽。
    • 推荐使用 napi_schedule_prep() 检查 + __napi_schedule() 调用的组合,确保在屏蔽中断后调度,避免竞态条件。

高级特性与配置

  1. ​实例映射​​:

    • 现代网卡多队列:通常一个 NAPI 实例对应一个中断源和一个队列对(RX+TX)。但映射关系灵活,可一对多或多对一。
  2. ​软件中断合并 (Coalescing)​​:

    • 可通过 gro_flush_timeoutnapi_defer_hard_irqs 参数配置 NAPI 在数据包处理完后启动一个定时器,延迟解除中断屏蔽,以合并更多数据包,减少中断次数,提升吞吐量。可通过 sysfs 或 Netlink (netdev-genl) ​​按每个 NAPI 实例​​进行配置。

      GRO(Generic Receive Offload,通用接收卸载)是 Linux 内核中的一种网络数据包处理机制,主要用于​​在接收数据时将多个小数据包合并成一个大包​​,再上传给网络协议栈处理,以此减少 CPU 的开销,提升网络吞吐性能

  3. ​忙轮询 (Busy Polling)​​:

    • 允许用户态进程主动轮询设备,在中断触发前检查并处理数据包,​​以 CPU 资源换取极低延迟​​。
    • 启用方式:套接字选项 SO_BUSY_POLL、系统参数 net.core.busy_pollio_uring API 或基于 epoll 的配置。
  4. ​基于 epoll 的忙轮询​​:

    • 可从 epoll_wait 调用中直接触发 NAPI 处理。
    • 要求:添加到同一 epoll 上下文的所有文件描述符应具有相同的 NAPI ID(可通过 SO_INCOMING_NAPI_ID 获取)。
    • 可通过 ioctl (EPIOCSPARAMS) 设置 epoll_params 结构,精细控制每个 epoll 上下文的忙轮询超时 (busy_poll_usecs)、预算 (busy_poll_budget) 和偏好 (prefer_busy_poll)。
  5. ​IRQ 缓解 (Mitigation) 与挂起 (Suspension)​​:

    • ​IRQ 缓解​​:使用 SO_PREFER_BUSY_POLL,承诺应用程序会定期忙轮询,驱动可​​长时间屏蔽中断​​。由 gro_flush_timeout 作为安全机制防止停滞。
    • ​IRQ 挂停​​:更激进的机制。当 epoll_wait 成功获取事件时,启动一个 irq-suspend-timeout 定时器来​​挂起(屏蔽)IRQ​​。只要应用程序持续处理,IRQ 就保持挂起状态,避免任何中断干扰。超时或处理空闲后 fallback 到常规模式。需要与 gro_flush_timeout 等配合使用。
  6. ​线程化 NAPI​​:

    • 可配置 NAPI 在​​独立的内核线程​​中运行,而不是软中断上下文。可针对每个网络设备或每个 NAPI 实例进行配置(通过 sysfs 或 Netlink)。
    • 优点:可能提供更可预测的调度或利于实时性(PREEMPT_RT)内核。
    • 建议:将 NAPI 线程固定到与处理其中断相同的 CPU 上。

用户空间交互

  • ​NAPI ID​​:每个 NAPI 实例有唯一 ID,用户空间可通过 SO_INCOMING_NAPI_ID 套接字选项获取。
  • ​查询与配置​​:可通过 Netlink (netdev-genl 家族) 查询设备队列信息(包括 NAPI ID)和配置每个 NAPI 实例的参数(如合并参数、线程化开关)。可使用 tools/net/ynl/ 下的工具(如 cli.py)进行操作。

核心思想​​:中断通知 + 轮询处理,平衡延迟与吞吐。

实现 poll 方法,正确使用 napi_schedule()napi_complete_done(),管理好中断屏蔽。通过软件中断合并、忙轮询、IRQ 缓解/挂起等机制针对低延迟或高吞吐场景进行精细配置。支持多种处理上下文(软中断、线程),映射关系(实例 - 中断 - 队列)灵活。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计