NAPI真的是kernel开发者词穷想的名字吧,你看看kernel里面各种名字,不知道为啥就不能起个好听点的。
言归正传,wiki:https://en.wikipedia.org/wiki/New_API 给出的解释是NAPI是一种用于网络设备的中断缓解的技术。
听起来比较抽象,想象下双十一当天的淘宝服务器,有撑破天际的网络数据从全世界各个角落汇聚到该服务器的每个网卡,也就意味着无时无刻该网卡都有大量数据进来,IRQ不断的产生, CPU正常调度不断被IRQ打断,网卡多累,CPU的效率多低。我这里把他称为纯中断方式。所以NAPI的核心想法就是减轻网卡的负担,不让网卡每次都响应中断,响应一次中断缓一缓,让子弹飞一会,数据攒一会,再响应中断, 收割积攒的数据,这样就使得效率高很多。
谈到NAPI,很多人的理解就仅仅是poll方式,好像我用了NAPI就是等于用了poll方式接收数据,其实不然。
NAPI的精髓是在poll方式 / 纯中断方式之间自由灵活的游走切换。
As a compromise, the Linux kernel uses the interrupt-driven mode by
default and only switches to polling mode when the flow of incoming
packets exceeds a certain threshold, known as the "weight" of the
network interface. --wiki
进一步说是,数据量不大的时候用纯中断,数据量很大的时候用poll的方式。那么什么时候switch,如何switch呢,且听我慢慢道来。
要搞懂NAPI,就要从它背后的逻辑和机制看起。要看懂此文,需要了解linux的中断处理的大致机制
I.
首先从三个重要的API开始:
netif_napi_add --driver告诉内核要使用napi的机制,初始化响应参数,注册poll的回调函数
napi_schedule --driver告诉内核开始调度napi的机制,稍后poll回调函数会被调用
napi_complete --driver告诉内核其工作不饱满即中断不多,数据量不大,改变napi的状态机,后续将采用纯中断方式响应数据
net_rx_action --内核初始化注册的软中断,注册poll的回调函数会被其调用
使用起来好像不是特别复杂。
II.
再来,我们看下几个重要的数据结构和原理。
softnet_data --这是一个PER_CPU的queue,更准确地说是一个和每个CPU绑定,属于该CPU的data queue,incoming packets are placed on per-CPU queues. 注意它的成员poll_list
struct softnet_data {
struct Qdisc *output_queue;
struct Qdisc **output_queue_tailp;
struct list_head poll_list; napi->poll_list结构挂入这个list,包括NAPI接口的driver以及非NAPI接口的driver都可以统一加入到这个poll_list
struct sk_buff *completion_queue;
...省略
};
napi_struct -- napi的关键结构,它也有一个poll_list,用于挂在softnet_data的poll_list上,是softnet_data poll list上的最小调度单位。还有一个weight需要着重说一下,这是每次调用poll回调函数时分配的最大skb数量,或者说从DMA buffer里面可以收割的最大的skb的数量。也就是前面wiki说的threshold。默认64,千兆网卡。netif_napi_add的时候注册。
struct napi_struct {
struct list_head poll_list; /* 用于加入处于轮询状态的设备队列 */
unsigned long state; /* 设备的状态 */
int weight; /* 每次处理的该设备的最大skb数量 */
int (*poll) (struct napi_struct *, int); /* 此设备的轮询方法 */
#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;
};
画个图简单说