ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NAPI杂记

NAPI杂记

jiaNAPI和tasklet区别:NAPI是利用的软中断NET_RX_SOFTIRQ实现, tasklet是利用TASKLET_SOFTIRQ实现

另外文章说NAPI主要是采用轮询,可以减少中断触发次数。do_softirq里面是开中断执行的啊?

中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了//这个是在哪里实现的啊??

NAPI是两者的结合,数据量低时采用中断,数据量高时采用轮询。平时是中断方式,当有数据到达时,会触发中断处理函数执行,中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了,因为中断处理函数中会轮询处理数据,直到没有新数据时才打开中断。

linux中断机制--理解中断上半部/下半部、软中断、tasklet、工作队列(可调度、可睡眠)_napi下半部-CSDN博客

如果clean_complete = true, 即队列的额度值都没用完,则会调用napi_complete_done 完成poll 工作,并通过ixgbe_irq_enable_queues重新开启硬中断,进入中断收包模式。

另外知乎文章里面提到通过ixgbe_irq_enable_queues重新开启硬中断,进入中断收包模式//这个重新开启硬件中断,看了一下并不是local_irq_enable之类的呢?难道NAPI提到的中断不是我理解的本cpu中断???

后面看了一下开启和关闭硬中断,并不是屏蔽整个cpu的中断,而是屏蔽单个irq的中断。ixgbe_irq_enable_queues这个函数就是实现的该功能 。所以关于重新让设备上报中断是在设备驱动的poll里面做的

中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了//这个是在哪里实现的啊??

至于文章最开始提到了如何关闭设备中断,看了一下,好像都是在中断上半部的函数里面做的:屏蔽设备的中断(例如某个中断,不同设备驱动,实现不一样),然后再触发一个软中断。最后走到poll函数里面,根据报文接收情况,在选择是否要继续处理,还是开启设备中断

------>2026/10/27

总结一下:

1、就是中断上半部屏蔽对应irq(不是本cpu所有中断),下半部通过net_rx_action调用poll驱动回调函数。

2、net_rx_action这个函数里面有个总的budget,这是防止poll_list上所有回调函数处理总的数量超过预算。另外就是单个poll里面有个weight,这个是防止单个poll处理超过预算。主要还是为了卡处理时间。

3、如果net_rx_action里面总的budget耗尽了也没有处理完poll_list则退出循环,重新触发NET_RX_SOFTIRQ

4、同样单个poll单个weight被耗尽也没有处理完。poll退出后也会被重新加入poll_list等待下一轮循环。反之就开启对应irq的中断

代码样例(ath10k)linux5.4.302

源码位置:Linux source code (v5.4.302) - Bootlin Elixir Cross Referencer

备注:代码没有验证过

1、添加NAPI

ath10k_pci_probe----->ath10k_pci_init_irq----->ath10k_pci_init_napi从这里看到回调函数就是ath10k_pci_napi_poll

将napi加到了该驱动的dev上面

void ath10k_pci_init_napi(struct ath10k *ar) { netif_napi_add(&ar->napi_dev, &ar->napi, ath10k_pci_napi_poll, ATH10K_NAPI_BUDGET); } void netif_napi_add(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight) { ................................ napi->poll = poll; ......................... napi->weight = weight; napi->dev = dev; .............................. list_add_rcu(&napi->dev_list, &dev->napi_list); napi_hash_add(napi); }

2、上半部注册

ath10k_pci_probe--->ath10k_pci_request_irq

static int ath10k_pci_request_irq(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); switch (ar_pci->oper_irq_mode) { case ATH10K_PCI_IRQ_LEGACY: return ath10k_pci_request_irq_legacy(ar); case ATH10K_PCI_IRQ_MSI: return ath10k_pci_request_irq_msi(ar); default: return -EINVAL; } }

假设上半部是这个ath10k_pci_request_irq_legacy

static int ath10k_pci_request_irq_legacy(struct ath10k *ar) { ............. ret = request_irq(ar_pci->pdev->irq, ath10k_pci_interrupt_handler, IRQF_SHARED, "ath10k_pci", ar); ........................ }

上半部做了3件事

(1)屏蔽了该irq

(2)将napi挂待了本cpu变量this_cpu_ptr(&softnet_data)的poll_list上面

(3)触发了软中断NET_RX_SOFTIRQ

static irqreturn_t ath10k_pci_interrupt_handler(int irq, void *arg) { ........................... ath10k_pci_disable_and_clear_legacy_irq(ar); ath10k_pci_irq_msi_fw_mask(ar); napi_schedule(&ar->napi); return IRQ_HANDLED; } void __napi_schedule_irqoff(struct napi_struct *n) { if (!IS_ENABLED(CONFIG_PREEMPT_RT)) ____napi_schedule(this_cpu_ptr(&softnet_data), n); else __napi_schedule(n); } static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi) { list_add_tail(&napi->poll_list, &sd->poll_list); __raise_softirq_irqoff(NET_RX_SOFTIRQ); }

3、下半部处理

do_soft_irq-->net_rx_action

static __latent_entropy void net_rx_action(struct softirq_action *h) { struct softnet_data *sd = this_cpu_ptr(&softnet_data); unsigned long time_limit = jiffies + usecs_to_jiffies(READ_ONCE(netdev_budget_usecs)); int budget = READ_ONCE(netdev_budget); ............................. local_irq_disable(); list_splice_init(&sd->poll_list, &list); local_irq_enable(); for (;;) { ........................... /* 遍历softnet_data的poll_list */ n = list_first_entry(&list, struct napi_struct, poll_list); budget -= napi_poll(n, &repoll); ............................. /* 总的处理时间超了*/ if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit))) { sd->time_squeeze++; break; } } ............................... /* 如果总时间超了还没有处理完则重新触发软中断 */ if (!list_empty(&sd->poll_list)) __raise_softirq_irqoff(NET_RX_SOFTIRQ); ...................... }
static int napi_poll(struct napi_struct *n, struct list_head *repoll) { /* 将napi从链表取下来 */ list_del_init(&n->poll_list); ................ if (test_bit(NAPI_STATE_SCHED, &n->state)) { /* 调用驱动回调ath10k_pci_napi_poll */ work = n->poll(n, weight); trace_napi_poll(n, work, weight); } /* 实际处理数量小于当前预算,即已被处理完了直接退出 */ if (likely(work < weight)) goto out_unlock; ..................... /* 预算耗完了还没有处理完,需要重新加入链表再次处理 */ list_add_tail(&n->poll_list, repoll); out_unlock: netpoll_poll_unlock(have); return work; }

调用驱动回调函数。若处理完了则调用napi_complete_done并开启中断

static int ath10k_pci_napi_poll(struct napi_struct *ctx, int budget) { ......................... ath10k_ce_per_engine_service_any(ar); done = ath10k_htt_txrx_compl_task(ar, budget); if (done < budget) { napi_complete_done(ctx, done); .............................. /* 预算耗尽前处理完了重新使能中断 */ ath10k_pci_enable_legacy_irq(ar); ath10k_pci_irq_msi_fw_unmask(ar); } out: return done; }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进