1NAPI是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的改善方法,和实验数据。使用NAPI先决条件:驱动可以继续使用老的2.4内核的网络驱动程序接口,NAPI的加入并不会导致向前兼容性的丧失,但是NAPI的使用至少要得到下面的保证:A.要使用DMA的环形输入队列(也就是ring_dma,这个在2.4驱动中关于Ethernet的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。B.在发送/接收数据包产生中断的时候有能力关断NIC中断的事件处理,并且在关断NIC以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称rx-ring)处理队列中。NAPI对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI就会强制执行dev-poll方法。而和不象以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。应当注意的是,经过测试如果DECTulip系列(DE21x4x芯片)以及NationalSemi的部分网卡芯片,的测试表明如果把从前中断处理的部分都改换用设备的POLL方法去执行,那么会造成轻微的延迟,因此在进行MII(介质无关)的操作上就需要一些小小的诀窍,详见mii_check_media的函数处理流程,本文不做详细讨论。在下面显示的例子表示了在8139中如何把处理过程放在dev的poll方法中,把所有的原来中断应该处理的过程放在了POLL方法里面,篇幅起见,我们只介绍接收的POLL方法。在下面的8139CP驱动程序介绍中表明了可以把在中断程序中所做的任何事情放在POLL方法中去做,当然不同的NIC在中断中所要处理的状态和事件是不一样的。对于所有的NIC设备,以下两种类型的NIC接收事件寄存器响应机制:COR机制:当用户程序读状态/事件寄存器,读完成的时候寄存器和NIC的rx-ring中表示的状态队列将被清零,natsemi和sunbmac的NIC会这样做,在这种情况下,必须把2NIC所有以前的中断响应的处理部分都移动到POLL方法中去。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.网络核心层(netcore)调用设备驱动程序使用循环方式发送数据包,在设备驱动层接收数据包的时候完全无锁的接收,而网络核心层则同样要保证每次只有一个处理器可以使用软中断处理接收队列。-3.在多个处理器对NIC的rx-ring访问的时刻只能发生在对循环队列调用关闭(close)和挂起(suspend)方法的时候(在这个时刻会试图清除接收循环队列)-4.数据同步的问题(对于接收循环队列来说),驱动程序是不需要考虑的网络层上的程序已经把这些事情做完了。-5.如果没有把全部的部分交给POLL方法处理,那么NIC中断仍然需要使能,接收链路状态发生变化和发送完成中断仍然和以前的处理步骤一样,这样处理的假设是接收中断是设备负载最大的的情况,当然并不能说这样一定正确。下面的部分将详细介绍在接收事件中调用设备的POLL方法。NAPI提供的重要函数和数据结构和函数:核心数据结构:structsoftnet_data结构内的字段就是NIC和网络层之间处理队列,这个结构是全局的,它从NIC中断和POLL方法之间传递数据信息。其中包含的字段有:structsoftnet_data{intthrottle;/*为1表示当前队列的数据包被禁止*/intcng_level;/*表示当前处理器的数据包处理拥塞程度*/intavg_blog;/*某个处理器的平均拥塞度*/structsk_buff_headinput_pkt_queue;/*接收缓冲区的sk_buff队列*/structlist_headpoll_list;/*POLL设备队列头*/structnet_deviceoutput_queue;/*网络设备发送队列的队列头*/structsk_buffcompletion_queue;/*完成发送的数据包等待释放的队列*/structnet_devicebacklog_dev;/*表示当前参与POLL处理的网络设备*/};核心API:31.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:4从POLL方法的本质意义上来说就在于尽量减少中断的数目,特别在于大量的小长度的数据包的时候,减少中断,以达到不要让整个操作系统花费太多的时间在中断现场的保护和恢复上,以便把赢得的时间用来在我网络层上的处理数据的传输,例如在下面介绍的8139CP中断的处理过程中,目的就在于尽快把产生中断的设备挂在poll_list,并且关闭接收中断,最后直接调用设备的POLL方法来处理数据包的接收,直到收到数据包收无可收,5或者是达到一个时间片内的调度完成。RTL8139C+的数据接收环形缓冲队列:RTL8139C+的接收方式是一种全新的缓冲方式,能显著的降低CPU接收数据造成的花费,适合大型的服务器使用,适合IP,TCP,UDP等多种方式的数据下载,以及连接IEEE802.1P,802.1Q,VLAN等网络形式;在8139CP中分别有64个连续的接收/发送描述符单元,对应三个不同的环形缓冲队列--一个是高优先级传输描述符队列,一个是普通优先级传输符描述队列,一个是接收符描述队列,每个环形缓冲队列右64个4个双字的连续描述符组成,每个描述符有4个连续的双字组成,每个描述符的开始地址在256个字节的位置对齐,接收数据之前,软件需要预先分配一个DMA缓冲区,一般对于传输而言,缓冲区最大为8Kbyte并且把物理地址链接在描述符的DMA地址描述单元,另外还有两个双字的单元表示对应的DMA缓冲区的接收状态。在/driver/net/8139CP.C中对于环形缓冲队列描述符的数据单元如下表示:structcp_desc{u32opts1;/*缓冲区状态控制符,包含缓冲区大小,缓冲区传输启动位*/u32opts2;/*专门用于VLAN部分*/u64addr;/*缓冲区的DMA地址*/};8139CP的NIC中断:staticirqreturn_tcp_interrupt(intirq,void*dev_instance,structpt_regs*regs){structnet_device*dev=dev_instance;structcp_private*cp=dev-priv;u16status;/*检查rx-ring中是否有中断到达*/status=cpr16(IntrStatus);if(!status||(status==0xFFFF))returnIRQ_NONE;if(netif_msg_intr(cp))printk(KERN_DEBUG%s:intr,status%04xcmd%02xcpcmd%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))6cp_tx(cp);/*如果发生链路变化的情况,需要检查介质无关接口(MII)的载波状态同样也发生变化,否则就要准备重新启动MII接口*/if(status&LinkChg)mii_check_media(&cp-mii_if,netif_msg_link(cp),FALSE);/*如果PCI总线发生错误,需要对8