NAPI是Linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就叫NAPI(New API),在2.5之后引入。简单来说,NAPI是综合中断方式与轮询方式的技术。中断的好处是响应及时,如果数据量较小,则不会占用太多的CPU事件;缺点是数据量大时,会产生过多中断,而每个中断都要消耗不少的CPU时间,从而导致效率反而不如轮询高。轮询方式与中断方式相反,它更适合处理大量数据,因为每次轮询不需要消耗过多的CPU时间;缺点是即使只接收很少数据或不接收数据时,也要占用CPU时间。
NAPI是两者的结合,数据量低时采用中断,数据量高时采用轮询。平时是中断方式,当有数据到达时,会触发中断
处理函数执行,中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了,因为中断处理函
数中会轮询处理数据,直到没有新数据时才打开中断。
很明显,数据量很低与很高时,NAPI可以发挥中断与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高
说低不低,则NAPI则会在两种方式切换上消耗不少时间,效率反而较低一些。
来看下NAPI和非NAPI的区别:
(1) 支持NAPI的网卡驱动必须提供轮询方法poll()。
(2) 非NAPI的内核接口为netif_rx(),NAPI的内核接口为napi_schedule()。
(3) 非NAPI使用共享的CPU队列softnet_data->input_pkt_queue,NAPI使用设备内存(或者
设备驱动程序的接收环)。
(1) NAPI设备结构
/* Structure for NAPI scheduling similar to tasklet but with weighting */
struct napi_struct {
/* The poll_list must only be managed by the entity which changes the
* state of the NAPI_STATE_SCHED bit. This means whoever atomically
* sets that bit can add this napi_struct to the per-cpu poll_list, and
* whoever clears that bit can remove from the list right before clearing the bit.
/
struct list_head poll_list; / 用于加入处于轮询状态的设备队列 /
unsigned long state; / 设备的状态 /
int weight; / 每次处理的最大数量,非NAPI默认为64 /
int (poll) (struct napi_struct , int); / 此设备的轮询方法,非NAPI为process_backlog() /
#ifdef CONFIG_NETPOLL
…
#endif
unsigned int gro_count;
struct net_device *dev;
struct list_head dev_list;
struct sk_buff *gro_list;
struct sk_buff *skb;
};
(2) 初始化
初始napi_struct实例。
void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
int (poll) (struct napi_struct , int), int weight)
{
INIT_LIST_HEAD(&napi->poll_list);
napi->gro_count = 0;
napi->gro_list = NULL;
napi->skb = NULL;
napi->poll = poll; / 设备的poll函数 /
napi->weight = weight; / 设备每次poll能处理的数据包个数上限 */
list_add(&napi->dev_list, &dev->napi_list); /* 加入设备的napi_list */
napi->dev = dev; /* 所属设备 */ #ifdef CONFIG_NETPOLL
spin_lock_init(&napi->poll_lock);
napi->poll_owner = -1; #endif
set_bit(NAPI_STATE_SCHED, &napi->state); /* 设置NAPI标志位 */ } (3) 调度 在网卡驱动的中断处理函数中调用napi_schedule()来使用NAPI。 /** * napi_schedule - schedule NAPI poll * @n: napi context * Schedule NAPI poll routine to be called if it is not already running. */ static inline void napi_schedule(struct napi_struct *n) {
/* 判断是否可以调度NAPI */
if (napi_schedule_prep(n))
__napi_schedule(n); } 判断NAPI是否可以调度。如果NAPI没有被禁止,且不存在已被调度的NAPI, 则允许调度NAPI,因为同一时刻只允许有一个NAPI poll instance。 /** * napi_schedule_prep - check if napi can be scheduled * @n: napi context * Test if NAPI routine is already running, and if not mark it as running. * This is used as a condition variable insure only one NAPI poll instance runs. * We also make sure there is no pending NAPI disable. */
static inline int napi_schedule_prep(struct napi_struct *n)
{
return !napi_disable_pending(n) && !test_and_set_bit(NAPI_STATE_SCHED, &n->state);
}
static inline int napi_disable_pending(struct napi_struct *n)
{
return test_bit(NAPI_STATE_DISABLE, &n->state);
}
enum {
NAPI_STATE_SCHED, /* Poll is scheduled /
NAPI_STATE_DISABLE, / Disable pending /
NAPI_STATE_NPSVC, / Netpoll - don’t dequeue from poll_list */
};
NAPI的调度函数。把设备的napi_struct实例添加到当前CPU的softnet_data的poll_list中,
以便于接下来进行轮询。然后设置NET_RX_SOFTIRQ标志位来触发软中断。
void __napi_schedule(struct napi_struct *n)
{
unsigned long flags;
local_irq_save(flags);
____napi_schedule(&__get_cpu_var(softnet_data), n);
local_irq_restore(flags);
}
static inline void ____napi_schedule(struct softnet_data sd, struct napi_struct *napi)
{
/ 把napi_struct添加到softnet_data的poll_list中 /
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ); / 设置软中断标志位 */
}
(4) 轮询方法
NAPI方式中的POLL方法由驱动程序提供,在通过netif_napi_add()加入napi_struct时指定。
在驱动的poll()中,从自身的队列中获取sk_buff后,如果网卡开启了GRO,则会调用
napi_gro_receive()处理skb,否则直接调用netif_receive_skb()。
POLL方法应该和process_backlog()大体一致,多了一些具体设备相关的部分。
(5) 非NAPI和NAPI处理流程对比
以下是非NAPI设备和NAPI设备的数据包接收流程对比图:
NAPI方式在上半部中sk_buff是存储在驱动自身的队列中的,软中断处理过程中驱动POLL方法调用
netif_receive_skb()直接处理skb并提交给上层。
/**
int netif_receive_skb(struct sk_buff skb)
{
/ 记录接收时间到skb->tstamp */
if (netdev_tstamp_prequeue)
net_timestamp_check(skb);
if (skb_defer_rx_timestamp(skb))
return NET_RX_SUCCESS;
#ifdef CONFIG_RPS
…
#else
return __netif_receive_skb(skb);
#endif
}
__netif_receive_skb()在上篇blog中已分析过了,接下来就是网络层来处理接收到的数据包了。
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 方法。而和不象以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。
应当注意的是,经过测试如果 DEC Tulip 系列(DE21x4x芯片)以及 National Semi 的部分网卡芯片,的测试表明如果把从前中断处理的部分都改换用设备的 POLL 方法去执行,那么会造成轻微的延迟,因此在进行 MII(介质无关)的操作上就需要一些小小的诀窍,详见 mii_check_media的函数处理流程,本文不做详细讨论。
在下面显示的例子表示了在 8139 中如何把处理过程放在 dev 的 poll 方法中,把所有的原来中断应该处理的过程放在了 POLL 方法里面,篇幅起见,我们只介绍接收的 POLL 方法。
在下面的 8139CP 驱动程序介绍中表明了可以把在中断程序中所做的任何事情放在 POLL 方法中去做,当然不同的 NIC 在中断中所要处理的状态和事件是不一样的。
对于所有的 NIC 设备,以下两种类型的 NIC 接收事件寄存器响应机制:
COR 机制:当用户程序读状态/事件寄存器,读完成的时候寄存器和NIC的rx-ring中表示的状态队列将被清零,natsemi 和 sunbmac 的 NIC 会这样做,在这种情况下,必须把 NIC 所有以前的中断响应的处理部分都移动到 POLL 方法中去。
COW 机制:用户程序写状态寄存器的时候,必须对要写的位先写 1 清 0,如下面要介绍的 8139CP 就是这样的类型,大多数的 NIC 都属于这种类型,而且这种类型对 NAPI 响应得最好,它只需要把接收的数据包处理部分放置在 POLL 方法中,而接收事件的状态处理部分放在原先的中断控制程序中,我们等下将要介绍的 8139CP 类型网卡就是属于这种类型。
C. 有防止 NIC 队列中排队的数据包冲突的能力。
当关断发送/接收事件中断的时候,NAPI 将在 POLL 中被调用处理,由于 POLL 方法的时候,NIC 中断已经不能通知包到达,那么这个时候在如果在完成轮询,并且中断打开以后,会马上有一个 NIC 中断产生,从而触发一次 POLL 事件,这种在中断关断时刻到达的包我们称为”rotting”;这样就会在 POLL 机制和 NIC 中断之间产生一个竞争,解决的方法就是利用网卡的接收状态位,继续接收环形队列缓冲 rx-ring 中的数据,直到没有数据接收以后,才使能中断。
锁定和防冲突机制: