NAPI In Linux Network Device Driver

dpdk:vfio-pci模式下iommu(N+Y)-Huge配置-numa配置 一、 l2fwd运行首先需要绑定网卡,绑定方式如下:(numa禁用+iommu禁用) PS:以挂载ens7 网卡,pci 0000:02:05.0 为例 1.关闭计划用dpdk接管的网卡接口,并查询其pci端口号,可以通过lspci |grep Ethernet查看。此时需要确认本机物理网卡或虚拟网卡为DPDK支持类型,查询网址https://core.dpdk.org/supported/ ifconfig ens37 down 2. 安装NIC网卡驱动模块并启动非安全NOIOMMU... 阅读详情

简介: NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以 POLL 的方法来轮询数据,类似于底半方式(bottom-half 的处理模式);但是目前在 Linux 的 NAPI 工作效率比较差,本文在分析 NAPI 的同时,提供了一种高效的改善方式供大家参考。


NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据,(类似于底半(bottom-half)处理模式);从我们在实验中所得到的数据来看,在随着网络的接收速度的增加,NIC 触发的中断能做到不断减少,目前 NAPI 技术已经在网卡驱动层和网络层得到了广泛的应用,驱动层次上已经有 E1000 系列网卡,RTL8139 系列网卡,3c50X 系列等主流的网络适配器都采用了这个技术,而在网络层次上,NAPI 技术已经完全被应用到了著名的 netif_rx 函数中间,并且提供了专门的 POLL 方法--process_backlog 来处理轮询的方法;根据实验数据表明采用NAPI技术可以大大改善短长度数据包接收的效率,减少中断触发的时间;由于 RTL8139CP 是一种应用比较广泛的网络适配器,所以本文以其为例,说明了NAPI技术在网络适配器上的应用和基本原理。

但是 NAPI 存在一些比较严重的缺陷:而对于上层的应用程序而言,系统不能在每个数据包接收到的时候都可以及时地去处理它,而且随着传输速度增加,累计的数据包将会耗费大量的内存,经过实验表明在 Linux 平台上这个问题会比在 FreeBSD 上要严重一些;另外采用 NAPI 所造成的另外一个问题是对于大的数据包处理比较困难,原因是大的数据包传送到网络层上的时候耗费的时间比短数据包长很多(即使是采用 DMA 方式),所以正如前面所说的那样,NAPI 技术适用于对高速率的短长度数据包的处理,在本文的末尾提出了 NAPI 的改善方法,和实验数据。


驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的加入并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证:

A. 要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。

B. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。

NAPI 对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI 就会强制执行dev->poll 方法。而和不象以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。

应当注意的是,经过测试如果 DEC Tulip 系列(DE21x4x芯片)以及 National Semi 的部分网卡芯片,的测试表明如果把从前中断处理的部分都改换用设备的 POLL 方法去执行,那么会造成轻微的延迟,因此在进行 MII(介质无关)的操作上就需要一些小小的诀窍,详见 mii_check_media的函数处理流程,本文不做详细讨论。

在下面显示的例子表示了在 8139 中如何把处理过程放在 dev 的 poll 方法中,把所有的原来中断应该处理的过程放在了 POLL 方法里面,篇幅起见,我们只介绍接收的 POLL 方法。

在下面的 8139CP 驱动程序介绍中表明了可以把在中断程序中所做的任何事情放在 POLL 方法中去做,当然不同的 NIC 在中断中所要处理的状态和事件是不一样的。

对于所有的 NIC 设备,以下两种类型的 NIC 接收事件寄存器响应机制:

  1. COR 机制:当用户程序读状态/事件寄存器,读完成的时候寄存器和NIC的rx-ring中表示的状态队列将被清零,natsemi 和 sunbmac 的 NIC 会这样做,在这种情况下,必须把 NIC 所有以前的中断响应的处理部分都移动到 POLL 方法中去。
  2. COW 机制:用户程序写状态寄存器的时候,必须对要写的位先写 1 清 0,如下面要介绍的 8139CP 就是这样的类型,大多数的 NIC 都属于这种类型,而且这种类型对 NAPI 响应得最好,它只需要把接收的数据包处理部分放置在 POLL 方法中,而接收事件的状态处理部分放在原先的中断控制程序中,我们等下将要介绍的 8139CP 类型网卡就是属于这种类型。

C. 有防止 NIC 队列中排队的数据包冲突的能力。

当关断发送/接收事件中断的时候,NAPI 将在 POLL 中被调用处理,由于 POLL 方法的时候,NIC 中断已经不能通知包到达,那么这个时候在如果在完成轮询,并且中断打开以后,会马上有一个 NIC 中断产生,从而触发一次 POLL 事件,这种在中断关断时刻到达的包我们称为"rotting";这样就会在 POLL 机制和 NIC 中断之间产生一个竞争,解决的方法就是利用网卡的接收状态位,继续接收环形队列缓冲 rx-ring 中的数据,直到没有数据接收以后,才使能中断。


- 1.SMP 的保证机制:保证同时只有一个处理器调用网络设备的 POLL 方法,因为我们将在下面看到同时只有一个处理器可以对调用 netif_rx_schedule 挂在 POLL 队列中的 NIC 设备调用POLL 方法。

- 2. 网络核心层(net core)调用设备驱动程序使用循环方式发送数据包,在设备驱动层接收数据包的时候完全无锁的接收,而网络核心层则同样要保证每次只有一个处理器可以使用软中断处理接收队列。

- 3. 在多个处理器对 NIC 的 rx-ring 访问的时刻只能发生在对循环队列调用关闭(close)和挂起(suspend)方法的时候(在这个时刻会试图清除接收循环队列)

- 4. 数据同步的问题(对于接收循环队列来说),驱动程序是不需要考虑的网络层上的程序已经把这些事情做完了。

- 5. 如果没有把全部的部分交给 POLL 方法处理,那么 NIC 中断仍然需要使能,接收链路状态发生变化和发送完成中断仍然和以前的处理步骤一样,这样处理的假设是接收中断是设备负载最大的的情况,当然并不能说这样一定正确。

下面的部分将详细介绍在接收事件中调用设备的 POLL 方法。


struct softnet_data 结构内的字段就是 NIC 和网络层之间处理队列,这个结构是全局的,它从 NIC中断和 POLL 方法之间传递数据信息。其中包含的字段有:

struct softnet_data
{
	int			throttle;	/*为 1 表示当前队列的数据包被禁止*/
	int			cng_level;	/*表示当前处理器的数据包处理拥塞程度*/
	int			avg_blog;	/*某个处理器的平均拥塞度*/
	struct sk_buff_head	input_pkt_queue;	/*接收缓冲区的sk_buff队列*/
	struct list_head	         poll_list;	/*POLL设备队列头*/
	struct net_device	         output_queue; 	/*网络设备发送队列的队列头*/
	struct sk_buff		completion_queue; /*完成发送的数据包等待释放的队列*/
struct net_device	backlog_dev;	/*表示当前参与POLL处理的网络设备*/
};


1. netif_rx_schedule(dev)

这个函数被中断服务程序调用,将设备的 POLL 方法添加到网络层次的 POLL 处理队列中去,排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,并且触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。

2. netif_rx_schedule_prep(dev)

确定设备处于运行,而且设备还没有被添加到网络层的 POLL 处理队列中,在调用 netif_rx_schedule之前会调用这个函数。

3. netif_rx_complete(dev)

把当前指定的设备从 POLL 队列中清除,通常被设备的 POLL 方法调用,注意如果在 POLL 队列处于工作状态的时候是不能把指定设备清除的,否则将会出错。


何在8139CP使用NAPI:

从 POLL 方法的本质意义上来说就在于尽量减少中断的数目,特别在于大量的小长度的数据包的时候,减少中断,以达到不要让整个操作系统花费太多的时间在中断现场的保护和恢复上,以便把赢得的时间用来在我网络层上的处理数据的传输,例如在下面介绍的 8139CP 中断的处理过程中,目的就在于尽快把产生中断的设备挂在 poll_list,并且关闭接收中断,最后直接调用设备的POLL方法来处理数据包的接收,直到收到数据包收无可收,或者是达到一个时间片内的调度完成。


RTL8139C+ 的接收方式是一种全新的缓冲方式,能显著的降低CPU接收数据造成的花费,适合大型的服务器使用,适合 IP,TCP,UDP 等多种方式的数据下载,以及连接 IEEE802.1P,802.1Q,VLAN等网络形式;在 8139CP 中分别有 64 个连续的接收/发送描述符单元,对应三个不同的环形缓冲队列--一个是高优先级传输描述符队列,一个是普通优先级传输符描述队列,一个是接收符描述队列,每个环形缓冲队列右 64 个4个双字的连续描述符组成,每个描述符有 4 个连续的双字组成,每个描述符的开始地址在 256 个字节的位置对齐,接收数据之前,软件需要预先分配一个 DMA 缓冲区,一般对于传输而言,缓冲区最大为 8Kbyte 并且把物理地址链接在描述符的 DMA 地址描述单元,另外还有两个双字的单元表示对应的 DMA 缓冲区的接收状态。

在 /driver/net/8139CP.C 中对于环形缓冲队列描述符的数据单元如下表示:

struct cp_desc {u32 opts1;/*缓冲区状态控制符,包含缓冲区大小,缓冲区传输启动位*/u32 opts2;/*专门用于VLAN部分*/u64 addr; /*缓冲区的DMA地址*/};

8139CP 的 NIC 中断:

static irqreturn_t
cp_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
{
	struct net_device *dev = dev_instance;
	struct cp_private *cp = dev->priv;
	u16 status;
/*检查rx-ring中是否有中断到达*/
	status = cpr16(IntrStatus);
	if (!status || (status == 0xFFFF))
		return IRQ_NONE;
	if (netif_msg_intr(cp))
		printk(KERN_DEBUG "%s: intr, status %04x cmd %02x cpcmd %04x\n",
		        dev->name, status, cpr8(Cmd), cpr16(CpCmd));
/*清除NIC中断控制器的内容*/
	cpw16(IntrStatus, status & ~cp_rx_intr_mask);
	spin_lock(&cp->lock);
/*接收状态寄存器表示有数据包到达*/
	if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr)) {
/*把当前的产生中断的NIC设备挂在softnet_data中的POLL队列上,等待网络上层上的应用程序处理*/
		if (netif_rx_schedule_prep(dev)) {
/*关闭接收中断使能*/
			cpw16_f(IntrMask, cp_norx_intr_mask);
			__netif_rx_schedule(dev);
		}
	}
/*发送中断的处理过程以及8139C+的专门软中断的处理过程,这里我们不关心*/
	if (status & (TxOK | TxErr | TxEmpty | SWInt))
		cp_tx(cp);
/*如果发生链路变化的情况,需要检查介质无关接口(MII)的载波状态同样也发生变化,
否则就要准备重新启动MII接口*/
	if (status & LinkChg)
		mii_check_media(&cp->mii_if, netif_msg_link(cp), FALSE);
/*如果PCI总线发生错误,需要对8139C+的设备重新复位*/
	if (status & PciErr) {
		u16 pci_status;
		pci_read_config_word(cp->pdev, PCI_STATUS, &pci_status);
		pci_write_config_word(cp->pdev, PCI_STATUS, pci_status);
		printk(KERN_ERR "%s: PCI bus error, status=%04x, PCI status=%04x\n",
		       dev->name, status, pci_status);
		/* TODO: reset hardware */
	}
	spin_unlock(&cp->lock);
	return IRQ_HANDLED;
}


在 8139CP 的中断程序可以看到 __netif_rx_schedule 的调用方式,它把 NIC 设备挂在softnet_data 结构中的 poll_list 队列上,以便及时的返回中断,让专门数据包处理 bottom-half部分来进行处理,我们先来看一下 __netif_rx_schedule 的内部工作流程。

static inline void __netif_rx_schedule(struct net_device *dev)
{
	unsigned long flags;
	local_irq_save(flags);
	dev_hold(dev);
/*把当前NIC设备挂在POLL(poll_list)队列中,等待唤醒软中断以后进行轮询*/
	list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
/*确定当前该设备所要准备接收的包大小*/
	if (dev->quota < 0)
		dev->quota += dev->weight;
	else
		dev->quota = dev->weight;
/*启动软中断,在表示所有中断的状态字irq_cpustat_t中关于软中断字段__softirq_pending中,
把关于网络轮循接收软中断位置1,等待调度时机来临时候运行该中断的句柄net_rx_action。*/
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	local_irq_restore(flags);
}

由 __netif_rx_schedule 启动的软中断的处理过程分析

软中断事件触发前已经在此设备子系统初始化时刻调用 subsys_initcall(net_dev_init) 在软中断控制台上被激活,挂在任务队列 tasklet 上准备在任务调度 schedule 的时刻运行它了,这个里面最主要的部分是调用了 8139C+ 网络设备的 POLL 方法(dev->poll),从网络设备的 rx-ring队列中获得数据,本来它应当放在网络设备中断服务程序中执行的,按照我们前面解释的那样,POLL方法以空间换取时间的机制把它放在软中断部分来执行轮循机制(采用类似老的 Bottom-half 机制也可以达到同样效果,而且更加容易理解一些)在每次进行进程调度的时候就会执行网络设备软中断,轮询 rx-ring 对 NIC 进行数据的接收。

软中断的处理过程:

static void net_rx_action(struct softirq_action *h)
{
	struct softnet_data *queue = &__get_cpu_var(softnet_data);
	unsigned long start_time = jiffies;
	int budget = netdev_max_backlog;/*表示队列的最大长度*/
/*锁定当前线程,多处理器的情况之下不能被其他处理器中断处理*/
	preempt_disable();
	local_irq_disable();
/*检查POLL队列(poll_list)上是否有设备在准备等待轮询取得数据*/
	while (!list_empty(&queue->poll_list)) {
		struct net_device *dev;
/*这里保证执行当前的 POLL 过程的时间不超过一个时间片,这样不至于被软中断占用太多的时间,
这样在一次调度的时间内执行完毕当前的 POLL 过程,budget 表示一个时间片内最大数据传输的"块数",
块的意思为每个 POLL 所完成 sk_buff数量,每块中间的 sk_buff 数量为 dev->quota 决定,在 8139CP 驱动中,
budget 为 300,而 quota 为 16 表示每给时间片最多可以接收到 4.8K 的 sk_buff 数量*/
		if (budget <= 0 || jiffies - start_time > 1)
			goto softnet_break;
		local_irq_enable();
/*从公共的 softnet_data 数据结构中的轮循队列上获得等待轮循的设备结构*/
		dev = list_entry(queue->poll_list.next,
				 struct net_device, poll_list);
/*调用设备的POLL方法从NIC上的Ring Buffer中读入数据*/
		if (dev->quota <= 0 || dev->poll(dev, &budget)) {
/*完成一次POLL过程的数据的接收,重新定义设备接收数据的"配额"
(事实上就是sk_buff缓冲区的数量,每次调用POLL方法的时候可以创建并且最
多可以向上层提交的sk_buff缓冲区数目,这个参数很重要在高速处理的时候有需要慎重优化这个数值,
在有大量数据接收的情况下,需要增加该数值)*/
			local_irq_disable();
			list_del(&dev->poll_list);
			list_add_tail(&dev->poll_list, &queue->poll_list);
			if (dev->quota < 0)
				dev->quota += dev->weight;
			else
				dev->quota = dev->weight;
		} else {
/*发生了错误的数据接收状况,或者没有完成"规定"配额的数据接收,并且没有新的数据进来,
这个也可能表示已经完成了传输的过程,调用__netif_rx_complete把网络设备从POLL队列上清除
(介绍POLL过程的时候详细介绍)*/
			dev_put(dev);
			local_irq_disable();
		}
	}
out:
	local_irq_enable();
	preempt_enable();
	return;
softnet_break:
	__get_cpu_var(netdev_rx_stat).time_squeeze++;
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	goto out;
}


dev->poll 方法:

这个方法通常被网络层在向驱动的接收循环队列获取新的数据包时刻调用,而驱动的接收循环队列中可以向网络层交付的包数量则在 dev->quota 字段中表示,我们来看 8139cp 中 POLL 的原型:

static int cp_rx_poll (struct net_device *dev, int *budget)

参数 budget 的上层任务所需要底层传递的数据包的数量,这个数值不能超过netdev_max_backlog 的值。

总而言之,POLL 方法被网络层调用,只负责按照网络层的要求值("预算"值)提交对应数量的数据包。8139CP 的 POLL 方法注册通常在设备驱动程序模块初始化(调用 probe)的时候进行,如下:

static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
… …
dev->poll = cp_rx_poll;
… …
}

设备的 POLL 方法正如前所说的是被网络层上的软中断 net_rx_action 调用,我们现在来看具体的流程:

static int cp_rx_poll (struct net_device *dev, int *budget)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned rx_tail = cp->rx_tail;
	/*设定每次进行调度的时候从设备发送到网络层次最大的数据包的大小*/
unsigned rx_work = dev->quota;
	unsigned rx;
rx_status_loop:
	rx = 0;
/*重新打开NIC中断,在 cp_interrupt 中断句柄中中断关闭了,现在 POLl 已经开始处理环行缓冲队列中的数据,
所以中断可以打开,准备接收新的数据包*/
	cpw16(IntrStatus, cp_rx_intr_mask);
	while (1) {/*POLL循环的开始*/
		u32 status, len;
		dma_addr_t mapping;
		struct sk_buff *skb, *new_skb;
		struct cp_desc *desc;
		unsigned buflen;
/*从下标为rx_tail的内存中的环行缓冲队列接收队列rx_skb上"摘下"套接字缓冲区*/
		skb = cp->rx_skb[rx_tail].skb;
		if (!skb)
			BUG();
		desc = &cp->rx_ring[rx_tail];
/*检查在 NIC 的环形队列(rx_ring)上的最后的数据接收状态,是否有出现接收或者 FIFO 的错误,是否*/
		status = le32_to_cpu(desc->opts1);
		if (status & DescOwn)
			break;
		len = (status & 0x1fff) - 4;
		mapping = cp->rx_skb[rx_tail].mapping;
		if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag)) {
			/* we don't support incoming fragmented frames.
			 * instead, we attempt to ensure that the
			 * pre-allocated RX skbs are properly sized such
			 * that RX fragments are never encountered
			 */
			cp_rx_err_acct(cp, rx_tail, status, len);
			cp->net_stats.rx_dropped++;
			cp->cp_stats.rx_frags++;
			goto rx_next;
		}
		if (status & (RxError | RxErrFIFO)) {
			cp_rx_err_acct(cp, rx_tail, status, len);
			goto rx_next;
		}
		if (netif_msg_rx_status(cp))
			printk(KERN_DEBUG "%s: rx slot %d status 0x%x len %d\n",
			       cp->dev->name, rx_tail, status, len);
		buflen = cp->rx_buf_sz + RX_OFFSET;
/*创建新的套接字缓冲区*/
		new_skb = dev_alloc_skb (buflen);
		if (!new_skb) {
			cp->net_stats.rx_dropped++;
			goto rx_next;
		}
		skb_reserve(new_skb, RX_OFFSET);
		new_skb->dev = cp->dev;
/*解除原先映射的环行队列上的映射区域*/
		pci_unmap_single(cp->pdev, mapping,
				 buflen, PCI_DMA_FROMDEVICE);
/*检查套接字缓冲区(sk_buff)上得到的数据校验和是否正确*/
		/* Handle checksum offloading for incoming packets. */
		if (cp_rx_csum_ok(status))
			skb->ip_summed = CHECKSUM_UNNECESSARY;
		else
			skb->ip_summed = CHECKSUM_NONE;
/*按照数据的实际大小重新定义套接字缓冲区的大小*/
		skb_put(skb, len);
		mapping =
		cp->rx_skb[rx_tail].mapping =
/*DMA影射在前面新创建的套接字缓冲区虚拟地址new_buf->tail到实际的物理地址上,
并且把这个物理地址挂在接收缓冲区的队列中*/
			pci_map_single(cp->pdev, new_skb->tail,
				       buflen, PCI_DMA_FROMDEVICE);
/*把新建立的缓冲区的虚拟地址挂在接收缓冲区的队列中,在下一次访问rx_skb数组的这个结构时候,
POLL方法会从这个虚拟地址读出接收到的数据包*/
		cp->rx_skb[rx_tail].skb = new_skb;
/*在cp_rx_skb调用netif_rx_skb,填充接收数据包队列,等待网络层在Bottom half队列中调用ip_rcv接收网络数据,
这个函数替代了以前使用的netif_rx*/
		cp_rx_skb(cp, skb, desc);
		rx++;
rx_next:
/*把前面映射的物理地址挂在NIC设备的环行队列上(也就是rx_ring上,它是在和NIC中物理存储区进行了DMA映射的,
而不是驱动在内存中动态建立的),准备提交给下层(NIC)进行数据传输*/
		cp->rx_ring[rx_tail].opts2 = 0;
		cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);
/*在相应的传输寄存器中写入控制字,把rx_ring的控制权从驱动程序交还给NIC硬件*/
		if (rx_tail == (CP_RX_RING_SIZE - 1))
			desc->opts1 = cpu_to_le32(DescOwn | RingEnd |
						  cp->rx_buf_sz);
		else
			desc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);
/*步进到下一个接收缓冲队列的下一个单元*/
		rx_tail = NEXT_RX(rx_tail);
		if (!rx_work--)
			break;
	}
	cp->rx_tail = rx_tail;
/*递减配额值quota,一旦quota递减到0表示这次的POLL传输已经完成了使命,
就等待有数据到来的时候再次唤醒软中断执行POLL方法*/
	dev->quota -= rx;
	*budget -= rx;
	/* if we did not reach work limit, then we're done with
	 * this round of polling
	 */
	if (rx_work) {
/*如果仍然有数据达到,那么返回POLL方法循环的开始,继续接收数据*/
		if (cpr16(IntrStatus) & cp_rx_intr_mask)
			goto rx_status_loop;
/*这里表示数据已经接收完毕,而且没有新的接收中断产生了,这个时候使能NIC的接收中断,
并且调用__netif_rx_complete把已经完成POLL的设备从poll_list上摘除,等待下一次中断产生的时候,
再次把设备挂上poll_list队列中。*/
		local_irq_disable();
		cpw16_f(IntrMask, cp_intr_mask);
		__netif_rx_complete(dev);
		local_irq_enable();
		return 0;	/* done */
	}
	return 1;		/* not done */
}

其他的使用 NAPI 的驱动程序和 8139CP 大同小异,只是使用了网络层专门提供的 POLL 方法--proecess_backlog(/net/dev.c),在 NIC 中断接收到了数据包后,调用网络层上的 netif_rx(/net/dev.c)将硬件中断中接收到数据帧存入 sk_buff 结构, 然后检查硬件帧头,识别帧类型, 放入接收队列(softnet_data 结构中的 input_pkt_queue 队列上), 激活接收软中断作进一步处理. 软中断函数(net_rx_action)提取接收包,而 process_backlog(也就是 POLL 方法)向上层提交数据。


我们现在来思考一下如何提高 NAPI 效率的问题,在说到效率这个问题之前我们先看一下在linux 的文档中 NAPI_HOWTO.txt 中提供一个模型用来构造自己 NIC 的 POLL 方法,不过和 8139 有一些不一样,其中 NIC 设备描述中有一个 dirty_rx 字段是在 8139CP 中没有使用到的。

dirty_rx 就是已经开辟了 sk_buff 缓冲区指针和已经提交到 NIC 的 rx_ring 参与接收的缓冲,但是还没有完成传输的缓冲区和已经完成传输的缓冲区的数量总和,与之相类似的是 cur_rx 这个表示的是下一个参与传输的缓冲区指针,我们在 NAPI_HOWTO.txt 的举例中可以看到这个字段的一些具体使用方式:



	/*cur_rx为下一个需要参与传输的缓冲区指针,
	如果cur_rx指针大于dirty_rx那么表示已经有在rx-ring中开辟的rx-ring中的每个传输缓冲已经被耗尽了,
	这个时候需要调用refill_rx_ring 把一些已经向网络层提交了数据的rx-ring接收单元开辟新的缓冲区,
	增加dirty_rx的数值,为下一次数据接收做准备,*/
        if (tp->cur_rx - tp->dirty_rx > RX_RING_SIZE/2 ||
            tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) 
                refill_rx_ring(dev);
/*如果已经当前的cur_rx和dirty_rx之间相差不超过总的rx_ring接收单元的一半,
而且剩下的一半中间有空的传输单元,那么我们不必担心了,因为还有足够的缓冲区可以使用(凭经验推断的),
就可以退出当前的程序,等待下一次软中断调用POLL来处理在这之间收到的数据,
(NAPI_HOWTO.txt中是重新启动时钟计数,这样做是在没有使能NIC中断处理的情况下)*/
        if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL)
                restart_timer();
/*如果执行到这里了,那表示有几种情况可能发生,第一当前的cur_rx和dirty_rx之间相差不超过总的rx_ring接收单元的一半,
调用refill_rx_ring后dirty_rx并未增加,(也许在rx-ring中大量的单元收到数据没有得到网络层函数的处理),
结果dirty_rx没有增加,而且也没有空闲的单元来接收新到的数据,这样就要重新调用netif_rx_schedule 来唤醒软中断,
调用设备的POLL方法,采集在rx-ring的数据。*/
	else netif_rx_schedule(dev);  /* we are back on the poll list */
	

在 RTL-8169 的驱动程序中就使用了 dirty_rx 这个字段,但是在 8139CP 中并未使用,其实这个并非 8139CP 驱动不成熟的表现,大家阅读 NAPI_HOWTO.txt 中可以知道,现在 8139CP 中并未严格按照 NAPI 所提出的要求去做,如果大家有兴趣的话,可以比较一下 8139CP 和 RTL-8169 这两个驱动之间的不同,大家会发现虽然两者都没有在 NIC 中断处理中去完成数据从驱动层向网络层上的转发,而都放在了软中断之中完成,但是在 8139 中利用了自己的一些独特的硬件特性,使 NIC 在利用关断中断接收数据的同时利用数据包到达位(RxOK)通知到达事件,然后采用 POLL 方法把数据从 NIC 直接转发到上层;而 RTL8169 还需要借助 softnet_data 结构中的 input_pkt_queue(套接字缓冲(sk_buff)输入队列)来完成从 NIC 中断到软中断之间的 sk_buff 数据调度;这样对于 8139CP 来说最大的好处就是不要了 dirty_rx 字段和 cur_rx 字段来让 POLL 方法以及 NIC 中断知道当前的传输单元的状况,还包括不需要不时定期的调用 refill_rx_ring 来刷新 rx-ring 获得空闲的传输单元;说到坏处我想就是重写了一个 POLL 方法,完全不能借用 /net/core/dev.c 中的 process_backlog 来作为自己的POLL方法,不过这个代价值得。

说了这么多,似乎都和提高效率没有关系,其实正好相反,通过这些了解我们对 softnet_data中的一些字段的意思应该更加清晰了,下面所叙述的,提高效率的方法就是在 8139CP 的基础上借用了 NAPI_ HOWTO.txt 中的一些方法,从实际上的使用效果来看,在某些应用场合之下比 Linux的 8139CP 的确是有了一定的提高,我们首先看看在 Linux2.6.6 的内核使用 8139CP 在x86(PIII-900Mhz)平台上的数据包接收处理情况:比较表如下:

Psize    Ipps       Tput     Rxint            Done
----------------------------------------------------
60     490000      254560      21              10
128     358750      259946      27              11
256     334454      450034      34              18
512     234550      556670      201239          193455
1024    119061      995645      884526          882300
1440     74568      995645      995645          987154

上表中表示:

"Pszie"表示包的大小
"Ipps" 每秒钟系统可以接收的包数量
"Tput" 每次POLL超过 1M 个数据包的总量
"Rxint" 接收中断数量
"Done" 载入 rx-ring 内数据所需要的 POLL 次数,这个数值也表示了我们需要清除 rx-ring 的次数。

从上表可以看出,8139CP 中当接收速率达到 490K packets/s 的时候仅仅只有 21 个中断产生,只需要 10 次 POLL 就可以完成数据从 rx_ring 的接收,然而对于大数据包低速率的情况,接收中断就会急剧增加,直到最后每个数据包都需要一次 POLL 的方法来进行处理,最后的结果就是每个中断都需要一次 POLL 的方法,最后造成效率的急剧下降,以至于系统的效率就会大大降低,所以 NAPI 适用于大量的数据包而且尽可能是小的数据包,但是对于大的数据包,而且低速率的,反而会造成系统速度的下降。

如果要改善这种情况,我们可以考虑采用以下的方法,我们在 MIPS,Xsacle 和 SA1100 平台上进行一系列的测试取得了良好的效果:

1. 完全取消 NIC 中断,使用 RXOK 位置控制接收中断,

2. 采用定时器中断 timer_list 的控制句柄,根据硬件平台设定一个合适的间隔周期(间隔周期依据平台不同而异),对 rx-ring 直接进行 POLL 轮询,我们在 MIPS 和 Xscale 上直接使用了中断向量 0--irq0 作为对 rx-ring 进行轮询的 top-half(注意我们在上述两个平台上选择的 HZ 数值是 1000,而通常这个数值是 100,并且重新编写了 Wall-time 的记数程序,让 Wall-Time 的记数还是以 10MS 为间隔),当然也可以根据自己的平台和应用程序的状况选择合适的定时时间。

3. 借助 softnet_data 中的 input_pkt_queue 队列,在时钟中断 bottom-half 中完成 POLL 方法之后,并不直接把数据传输到网络层进行处理,而是把 sk_buff 挂在 input_pkt_queue队列上,唤醒软中断在过后处理,当然可以想象,这样需要付出一定的内存代价,而且实时性能也要差一些。

4. 使用 dirty_rx 字段和 refill_rx_ring 函数,在调用完 POLL 方法以后,而且网络层程序比较空闲的时候为一些 rx-ring 中的单元建立新缓冲挂在环形缓冲队列上,这样可以在新的数据包达到的时候节省时间,操作系统不必要手忙脚乱地开辟新的空间去应付新来的数据。

5. 最后请注意:我们上层的应用程序是以网络数据转发为主的,并没有在应用层面上有很多后台进程的复杂的应用,上述的 1 到 4 点中所做的显而易见是以牺牲系统效率整体效率而单独改善了网络数据的处理。

我们再来看改善的 8139CP 驱动程序使用 8139CP 在 x86(PIII-900Mhz) 平台上的接收情况:

Psize    Ipps       Tput     Rxint       Done
----------------------------------------------------
60     553500    354560   17         7
128     453000    350400   19         10
256     390050    324500   28         13
512     305600    456670   203        455
1024     123440    340020   772951     123005
1440     64568     344567   822394     130000

从上图来看,数据传输的效率和波动性有很明显的改善,在高速率和低速率的时候所需要的POLL 次数的差距以前的 8139CP 驱动程序那么显著了,这样的而且最大的包接收能力也提高到了 553K/s,我们在 MIPS 系列以及 Xscale 系列平台上最大的包接收能力可以提高大约 15%-25%。

最后使用 NAPI 并不是改善网络效率的唯一途径,只能算是权益之策,根本的解决途径还是在于上层应用程序能够独占网络设备,或者是提供大量的缓冲资源,如果这样,根据我们的实验数据表明可以提高 100%-150% 以上的接收效率。


鸿蒙适配Flutter网络工具库实战与优化 网络工具库是现代软件开发中的基础组件,通过封装底层网络协议栈实现高效的端口扫描、设备发现等功能。其核心原理是利用TCP/IP协议族中的套接字编程接口,结合多线程技术提升扫描效率。在跨平台场景下,网络工具库需要适配不同操作系统的网络API差异,这正是鸿蒙系统与Flutter生态融合的技术难点。通过NAPI封装ohos.net.connection模块,开发者可以构建支持零信任安全模型的网络诊断工具,应用于智能家居设备发现、企业内网安全审计等场景。本次实践特别针对鸿蒙的线程调度和内存管理机制进行了深度优化,使网 阅读详情

相关推荐

DPAA2架构解析:从硬件抽象到Linux驱动的嵌入式网络加速实践

在现代嵌入式系统与高性能网络处理器中,硬件加速技术是实现极致性能与能效的关键。其核心原理在于通过架构解耦,将计算密集、模式固定的任务从通用CPU卸载到专用硬件单元,从而显著提升吞吐量、降低延迟并优化功耗。这一技术价值在5G基站、边缘网关和数据中心网卡等场景中尤为凸显。DPAA2(数据路径加速架构第二代)正是这一理念的杰出代表,它通过引入管理复合体(MC)和对象化抽象模型,将复杂的硬件资源封装为DPNI、DPSECI等逻辑对象,极大地简化了驱动开发。本文聚焦于DPAA2的Linux驱动实践,深入剖析了以太网驱

cr147369的博客 366

一文讲透Linux网络设备驱动框架及编写步骤

一文讲透Linux网络设备驱动框架及编写步骤

qq_41483419的博客 5309

LINUX网卡驱动分析――Intel(R) PRO/100 Network Driver

 LINUX网卡驱动分析――Intel(R) PRO/100 Network Driver最近学习LINUX驱动开发,看的是《LINUX DEVICE DRIVER》这本书,差不多能看懂,不过说实在的,都是些理论上的东西,没有什么实践,感觉提升比较慢,所以想拿LINUX自带的E100网卡驱动来分析和学习一下,看看人家大师们怎么写驱动的。然后如果有时间再写一个关于我的开发板的S3C2410上

雨雪逝去(Sambios)的专栏 1万+

Linux NAPI机制分析

1、概述 在NAPI之前,网卡每收到一个包就会触发一个中断通知cpu读取数据包,当数据包比较多时,中断数过多势必影响cpu性能,因此Linux引入NAPI机制,NAPI就是在收到中断后,先将网卡收包模式切换成poll模式,等收包完成后重新进入中断模式,本节主要分析Linux的NAPI实现机制。 NAPI的主要流程如下图,物理网卡收到包后触发irq中断通知cpu(触发中断后,默认disable该中断),中断上半部处理里将网卡设备的napi->poll_list加入到softnet_data->

zheng的博客 6221

【10】route,iptables,macvlan,mii/mdio,ncsi,bond,dns,ipv6

如下53134相当于一个小的switch交换机(固件文件小,所以用eeprom,bmc用spi去升级这个eeprom),53134 port1口给bmc,port2口给cpu,一根网线,cpu和bmc都有不同的ip。bmc中ifconfig会多出eth1(顺位,原来有eth0,不是eth3),ssh admin@eth1_ip和ssh root@eth1_ip能登上cpu和bmc,cpu端ifconfig没变化,如下phy-mode不写也可以。如下在烧录的机器上烧录镜像后,vi第一行.dep文件。

持续更新ing... 149

【信息科学与工程学】【通信工程】第一百九十一篇 路由器体系结构03

编号类型领域问题详细的数学分析(逐步推理)参数列表(表达式 / 数值范围 / 边界 / 测量)关联知识839DCI演进 · 收敛DCI 2030 年三大技术收敛方向步骤1:方向一:IPoDWDM 成为绝对主流(Omdia 2027 59% → 2030 80%+),OTN 退化为 OLS 纯光层。步骤2:方向二:800ZR+ 取代 400ZR 成为主力速率,PCS 使 800G 达 600–1000km。步骤3:方向三:AI 集群 DCI 催生 1.6T/3.2T 超短距(<10km)相干模块(OSFP-X

weixin_49199313的博客 861

linux网络流程分析(一)---网卡驱动

分析linux网络的书已经很多了,包括《追踪Linux TCP/IP代码运行》《Linux内核源码剖析——TCP/IP实现》,这里我只是从数据包在linux内核中的基本流程来分析,尽可能的展现一个主流程框架。 内核如何从网卡接收数据,传统的过程:1.数据到达网卡;2.网卡产生一个中断给内核;3.内核使用I/O指令,从网卡I/O区域中去读取数据; 我们在许多网卡驱动中(很老那些)...

weixin_30642267的博客 862

《Linux network driver development Training lab book》

个人认为最经典的学习资料,从无到有地讲述了网卡驱动的编写过程,基于Atmel的MPU的Ethernet Controller。

Linux网络驱动学习

正点原子LInux驱动学习之网络驱动

qq_42049394的博客 2680

【SPSS实战】手动实现Quade非参数协方差分析:从数据准备到结果解读

本文详细介绍了在SPSS中手动实现Quade非参数协方差分析的实战方法。当数据不满足正态分布等参数检验假设时,通过将协变量和因变量转换为秩,并利用秩回归残差进行Kruskal-Wallis组间比较,提供了一种清晰、可操作的近似解决方案。该方法有效解决了非正态数据的协变量调整问题,适用于实验研究和调查数据分析。

bean的博客 468

zabbix6.0.4拓扑图链路显示流量

1. 背景介绍 网络流量监控是网络管理员必备功课,以前研究cacti的weathermap插件,网络流量大小和拥塞情况一清二楚,随着cacti的没落,weathermap插件也不能再继续使用,此时就需要学习了解新的替代方案。 经过研究和实际测试,发现zabbix的拓扑图有这个功能。 以下验证了zabbix的拓扑图显示线路流量的功能。 2. Zabbix 6显示拓扑图流量过程 选择监测-拓扑图 点进Local ne...

u013930899的博客 8483

深入理解Linux网络技术内幕 P1-P2

细品

西门吹雪 540

Vivado ILA抓不到低频信号?试试Capture Control模式,保姆级配置教程(附PWM调试实例)

本文详细解析了Vivado ILA的Capture Control模式在低频信号捕获中的应用,特别针对PWM调试等场景提供保姆级配置教程。通过条件采样和时间拉伸技术,有效解决传统ILA采样在低频信号调试中的存储深度不足、关键信号丢失等问题,并附赠呼吸灯PWM的完整调试实例,帮助FPGA开发者提升调试效率。

weixin_42549451的博客 350

终极rtl88x2bu驱动安装指南:专业级Linux Wi-Fi适配器解决方案

rtl88x2bu驱动是为Linux系统开发的专业级开源Wi-Fi驱动程序,专门支持使用Realtek rtl88x2bu芯片的USB无线适配器。这款驱动程序基于Realtek官方5.6.1.6版本源代码进行了全面更新和优化,确保与当前主流Linux内核版本完美兼容,为技术开发者和系统管理员提供稳定可靠的Wi-Fi连接解决方案。 ## 项目概述与技术架构 rtl88x2bu驱动采用模块化设计,

gitblog_00551的博客 1041

petalinux i2c的配置

1.硬件平台配置 1.内核配置 cd petalinux-config -c kernel -> Device Drivers -> I2C support -> i2C Hardware Bus support -> Xilinx I2C Controller Pressing 启用CONFIG_I2C_XILINX=y 2.修改用户设备树 cd cd project-spec/meta-user/recipes-bsp/device-tree/files/ i2c2:

u013403954的博客 4313

YOLO11涨点优化:创新多尺度卷积注意力(MSCA)模块,小目标检测神器!

MSCA模块通过创新的多尺度设计,在保持计算效率的同时显著提升小目标检测能力。实验证明,在YOLOv5/v8等模型上嵌入MSCA后,mAP提升超过2%,小目标检测精度提升近4%,且计算代价增加极小。注意力机制的未来发展方向动态核尺寸:根据输入特征自适应调整卷积核大小跨模态融合:结合RGB与深度/热力图等多模态信息自监督优化:利用对比学习预训练注意力模块尝试在你的YOLO模型中插入MSCA模块,欢迎在评论区分享你的涨点结果!参考文献。

QQ_778132974的博客 470

Linux DSA双KSZ9477交换机级联配置实战与优化指南

分布式交换架构(DSA)是Linux内核中用于管理CPU与多端口交换机芯片协同工作的核心框架,它通过将交换机端口虚拟化为独立网络接口,实现了灵活的网络配置与管理。其原理在于将CPU端口与交换机用户端口解耦,并通过设备树(Device Tree)精确描述硬件拓扑,使内核能统一识别和管理交换网络。该技术对于构建高性能、可扩展的嵌入式网络设备(如工业网关、路由器)具有重要价值,能有效支持VLAN、链路聚合等高级功能。在应用场景上,DSA尤其适用于需要连接多颗交换芯片以扩展端口数量的复杂硬件设计。本文聚焦于利用DS

weixin_30522183的博客 571

智能工厂评估模型研究

摘要:通过对国内外现有智能制造评估模型的研究,结合重庆制造业行业领跑者与落后者之间差距大的现状,以实现全要素全连接的数字化工厂为出发点,融合工业互联网标识,构建实践可行的智能工厂评估模型推动中小型企业参与智能制造转型,缓和两极分化问题。本评估模型包含工厂体系架构的3个层级,提供用于推动智能制造转型的参考,包括设计、生产、管理的全过程评估。关键词:智能制造评估模型;智能制造转型;全连接工厂;工业互联网标识0 引言受行业需求与技术创新的影响,全球制造业在历经了工业1.0机械化、工业2.0电气化和工业3.0自动

罗伯特技术屋 677

Linux网络协议栈:NAPI机制与处理流程分析(图解)

Linux网络协议栈:NAPI机制与处理流程分析(图解)_RToax-优快云博客_netif_napi_addTable of ContentsNAPI机制NAPI缺陷使用 NAPI 先决条件非NAPI帧的接收netif_rx -将网卡中收到的数据包放到系统中的接收队列中enqueue_to_backlog____napi_schedule函数NAPI方式NAPI帧的接收NAPI接口struct napi_struct结构 -内核处理软中断的入口netif_napi_add函数 -驱动初始时向内核注册软软

maimang1001的专栏 5208

算法 | A*算法实现最优路径规划

启发式探索是利用问题拥有的启发信息来引导搜索,达到减少探索范围、降低问题复杂度的目的。A*寻路算法是启发式探索的一个典型实践,在寻路搜索的过程中,给每个节点绑定了一个估计值(即启发式),在对节点的遍历过程中采取估计值优先原则,估计值更优的节点会被优先遍历。...

不断学习,不断进步,提高自己 6630

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

NAPI技术--原理和实现(一)

概述 NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就叫NAPI(New API),在2.5之后引入。 简单来说,NAPI是综合中断方式与轮询方式的技术。 中断的好处是响应及时,如果数据量较小,则不会占用太多的CPU事件;缺点是数据量大时,会产生过多中断,而每个中断都要消耗不少的CPU时间,从而导致效率反而不如轮询高。轮询方式与中断方式相反,它更适合处理大量数据,因为每次轮询不需要消耗过多的CPU时间;缺点是即使只接收很少数据或不接收数据时,也要占用CPU时间。 NAPI是两者

bob的博客 5068

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

浅谈 Linux 内核开发之网络设备驱动

网络设备介绍 网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。众所周知,在 OSI(Open Systems Interconnection,开放网际互连)中,网络被划分为七个层次,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。我们所讲的网络设备也包括两个层次,一层叫做 MAC(Media Access Contr

david104的专栏 1134

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

网络性能优化(NAPI)

<br /><br />发送路径上的优化<br />TSO (TCP Segmentation Offload)<br />  TSO (TCP Segmentation Offload) 是一种利用网卡分割大数据包,减小 CPU 负荷的一种技术,也被叫做 LSO (Large segment offload) ,如果数据包的类型只能是 TCP,则被称之为 TSO,如果硬件支持 TSO 功能的话,也需要同时支持硬件的 TCP 校验计算和分散 - 聚集 (Scatter Gather) 功能。<br />  

少帅的天空 6186

Linux network driver

一、常见问题 1)2.6.32内核不兼容I219网卡 http://exxactcorp.com/blog/how-to-installconfigure-intel-i219-network-adapter-in-centos-6/ 转载于:https://www.cnblogs.com/274914765qq/p/6180915.html...

weixin_30466421的博客 173

napi

本文转载至:https://wiki.linuxfoundation.org/networking/napi NAPI (“New API”) is an extension to the device driver packet processing framework, which is designed to improve the performance of high-speed ne

闲云孤鹤 1164
上一篇: Linux Notification chain学习
下一篇: IEEE 802.11 RTS/CTS
kernel_learner
博客等级 码龄15年 60粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值