NAPI介绍
NAPI混合了中断和轮询,在高负载情况下可以有效减少CPU的负载,从而提高性能,从这一点可以认识到一种优化的方式就是降低CPU负载,例如DMZ,就是另一个例子,将更多的工作交给其他硬件来执行,而不是CPU,从而达到减轻CPU压力。
NAPI的原理:当内核正在处理接收数据包中断,它将继续轮询处理网卡上的数据包,而不是每次处理完成后就退出,省去了每次中断通知的时间。
NAPI的优势:
减少CPU负载,因为更少的中断处理。
更公平的处理每一个网卡设备。
Linux数据接收是通过软中断来实现的,下面是注册的两个软中断:
open_softirq(NET_TX_SOFTIRQ,net_tx_action);
open_softirq(NET_RX_SOFTIRQ,net_rx_action);
static void net_rx_action(structsoftirq_action *h)
在这之前先看一个全局变量,softnet_data,这是一个per-CPU变量,定义如下:
DEFINE_PER_CPU_ALIGNED(structsoftnet_data, softnet_data);
poll_list所有需要遍历的网卡,后面将介绍如何产生的。
在net_rx_action将会便利poll_list中的网卡,然后调用其poll函数来处理。
这里忽略了细节,例如如何控制轮询遍历。
前面简单的说了是通过软中断来实现调用net_rx_action的,浅尝辄止和刨根究底是两种可以合并的学习方式,浅尝辄止可以快速的了解整件事情,但是刨根究底则可以学习到更多本质的东西,是不是有点类似于中断和轮询?
看e1000e,这是intel的网卡,pci架构的网卡看代码要从probe看起,因为这里才是真正发现硬件并进行处理的地方。
static int __devinit e1000_probe(structpci_dev *pdev,
const struct pci_device_id *ent)
关注netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);
void netif_napi_add(struct net_device *dev,struct napi_struct *napi,
int (*poll)(struct napi_struct *, int), intweight)
就是给napi结构体赋值,关注e1000_clean这个就是poll函数,我们上面提到的,后面还要继续深入分析。
接着就是看中断处理函数,中断处理函数一般命名都是intr,所以用sourceinsight搜索一下intr就出来了。
static irqreturn_t e1000_intr(int irq, void*data)
跳过前面的检查,看重要的部分:
if(napi_schedule_prep(&adapter->napi)) {
adapter->total_tx_bytes= 0;
adapter->total_tx_packets= 0;
adapter->total_rx_bytes= 0;
adapter->total_rx_packets= 0;
__napi_schedule(&adapter->napi);
}
__napi_schedule负责将napi加入到softnet_data的列表,现在我们就知道为什么在软中断处理函数net_rx_action中进行遍历了。
list_add_tail(&napi->poll_list,&sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
从这里我们可以看到,只要是中断产生在同一个cpu,那么对应的数据包就是在一个cpu上的,且按照NAPI的设计,大多数数据包都应该是在同一个cpu上,因为产生一次中断就进行多次读取,如果没有读完的,是否可能在不同cpu上面呢?通过cpu绑定可以实现具体网卡到哪个cpu上处理。
http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/
同一个会话呢?我们可以断定的是去包和回包在不同cpu上处理是非常有可能的,除非利用绑定cpu处理,但是绑定cpu处理是非常不灵活的,例如一个8核的机器只跑一对网卡。
有点乱?从上面的代码我们可以简单的说:除非硬件中断发生在同一个cpu上面,否则不可能在同一个cpu上进行处理数据,这就要求硬件支持,或者是利用cpu的亲和力。
更详细的讨论http://bbs.chinaunix.net/thread-1979496-1-1.html
E1000e的poll函数是e1000_clean,驱动本身搞的比较复杂了,先不考虑太多,最后调用了netif_receive_skb,用于接收数据进行处理,往后就是网络层的事情,当然或许是vlan和网桥,无所谓啦。这里我们可以得出结论,我们处理完数据包可以直接调用netif_receive_skb来提交给内核继续处理,如果是调用netif_rx则多一次软中断处理。
再讨论一下旧的api,新旧之间的区别在于旧的api是在中断中直接读取数据然后交给netif_rx,而新的依赖于poll函数,poll函数负责读取数据。
分析一下netif_rx函数。同样也依赖于softnet_data,只是将数据包挂到input_pkt_queue中,然后一样softirq来处理,也就是说和我们上面分析一样,那么它的poll函数是什么呢?
我们看net_dev_init初始化的时候给初始化为process_backlog,也同样是调用内部函数__netif_receive_skb。