NAPI(New API)的一些浅见

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;

};

画个图简单说:


所以一个napi_structsoftnet_data的poll list会发生的故事只可能有:

a. enqueue, 入队列

b. dequeue, 出队列

c. reorder, 重新调整队列顺序

III.

那么napi_struct什么时候enqueue挂在softnet_data的poll list,以及什么时候reorder移动它,或者dequeue从list里面去掉,不妨先了解下整个NAPI工作的流程


图侵删

请注意上面的图不是软件流程图,只是大概的因果关系,具体怎么回事等我细细道来,

1. HW ISR,具体来说算是DMA中断,告诉CPU搬砖完成,这样会通过触发中断最终触发driver注册到内核的中断函数,例如注册的PCI的中断.

2. 在这个IRQ函数中,通常driver会disable和clear IRQ,比如关PCI中断,然后调用napi_schedule,它所作的事情就是enqueue,然后mask一个NET_RX_SOFTIRQ event

3.接下来不久,当中断上半部结束,开始中断下半部的时候,会在__do_softirq中发现先前mask的NET_RX_SOFTIRQ有效,net_rx_action作为其callback会被调用。在net_rx_action会调用driver注册的poll函数。net_rx_action这个最重要的函数还是要看下的。

static void net_rx_action(struct softirq_action *h) //from kernel 3.13

{

struct softnet_data *sd = &__get_cpu_var(softnet_data);

unsigned long time_limit = jiffies + 2;

int budget = netdev_budget;//300

void *have;

local_irq_disable();

while (!list_empty(&sd->poll_list)) {

struct napi_struct *n;

int work, weight;

/* If softirq window is exhuasted then punt.

* Allow this to run for 2 jiffies since which will allow

* an average latency of 1.5/HZ.

*/

if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit)))

goto softnet_break;

local_irq_enable();

/* Even though interrupts have been re-enabled, this

* access is safe because interrupts can only add new

* entries to the tail of this list, and only ->poll()

* calls can remove this head entry from the list.

*/

n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

have = netpoll_poll_lock(n);

weight = n->weight;

/* This NAPI_STATE_SCHED test is for avoiding a race

* with netpoll's poll_napi().  Only the entity which

* obtains the lock and sees NAPI_STATE_SCHED set will

* actually make the ->poll() call.  Therefore we avoid

* accidentally calling ->poll() when NAPI is not scheduled.

*/

work = 0;

if (test_bit(NAPI_STATE_SCHED, &n->state)) {

work = n->poll(n, weight);

trace_napi_poll(n);

}

WARN_ON_ONCE(work > weight);

budget -= work;

local_irq_disable();

/* Drivers must not modify the NAPI state if they

* consume the entire weight.  In such cases this code

* still "owns" the NAPI instance and therefore can

* move the instance around on the list at-will.

*/

if (unlikely(work == weight)) {

if (unlikely(napi_disable_pending(n))) {

local_irq_enable();

napi_complete(n);

local_irq_disable();

} else {

if (n->gro_list) {

/* flush too old packets

* If HZ < 1000, flush all packets.

*/

local_irq_enable();

napi_gro_flush(n, HZ >= 1000);

local_irq_disable();

}

list_move_tail(&n->poll_list, &sd->poll_list);

}

}

netpoll_poll_unlock(have);

}

out:

net_rps_action_and_irq_enable(sd);

#ifdef CONFIG_NET_DMA

/*

* There may not be any more sk_buffs coming right now, so push

* any pending DMA copies to hardware

*/

dma_issue_pending_all();

#endif

return;

softnet_break:

sd->time_squeeze++;

__raise_softirq_irqoff(NET_RX_SOFTIRQ);

goto out;

}

一条条说:

1) 每次NET_RX这个软中断也会分配一个budget,默认300,含义见下条。


int budget = netdev_budget;//300


2)每次都从list头取个napi_struct,然后调poll回调函数。结合之前的napi的budget,我们大概明白,poll回调函数会消耗软中断的budget,如果这个poll list的poll消耗完了软中断的所有budget(300),则循环退出,按照每次poll消耗都是64来算也就是5次。这个时候poll list还有待poll的napi struct。另一个退出条件是超时2 jiffies。这个两种条件一种是poll list上的poll收割的数据都是大包,所以时间没超时但是总的budget用完。另一种情况就是都是数量庞大的小包,但是都没超budget,最后超时。然后不管哪种情况,都会mask一个NET_RX_SOFTIRQ event。留待下次net_rx_action处理。


if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit)))

goto softnet_break; ...省略

n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list); ... 省略

work = n->poll(n, weight); ... 省略

budget -= work;... 省略

__raise_softirq_irqoff(NET_RX_SOFTIRQ);


3)Driver的poll函数通常每家实现不一样,大概逻辑就是通常会从dma ring里面把数据拿出来,最多拿napi的注册weight(默认64)的skb。

如果ring里面的数据不够weight,注意了,这里很关键,就会认为,数据已经拿完,执行napi_complete, 将该napi_struct从list里面拿掉,并且开启中断,比如开启PCI,进入纯中断模式;这之后有可能有新的中断触发,于是新的napi_struct在HW ISR中(抢占软中断)被加入到poll list,而且那个时候for循环还没有退出。

如果ring里面数据太多,那就只能拿到weight这么多,注意了,这里不会将napi_struct从list上拿掉,也不会关中断,poll函数返回时会在net_rx_action中返回消耗的budget,如果消耗到最大值weight(64),说明还有数据要取,这样会放到poll list的尾部(假设不考虑GRO的情况)。留待下次到队头再次poll。这时候是switch到了poll模式


work = n->poll(n, weight); ... 省略

if (unlikely(work == weight)) {

if (unlikely(napi_disable_pending(n))) {

... 省略

} else {

if (n->gro_list) {

... 省略

}

list_move_tail(&n->poll_list, &sd->poll_list);

}


只有在poll回调函数里面认为已收割数据不超过weight,中断才打开,才会有新的中断进来,才会有新的napi_struct加进list(queue)。

只要poll回调函数里面的中断没有打开,比如PCI,都属于poll模式,在net_rx_action的budget(300)和2 jiffies时间用完时,退出for循环, 不一定是poll模式,比如一直都是小包的且很频繁。也有可能会退出。


图侵删

4. 非正常退出时,list上一定还是有napi_struct的,那么非正常退出之后又发生了什么?无论是哪种非正常退出(netdev_budget 用完 or 超时2 jiffies),新的NET_RX_SOFTIRQ mask会置起来,那么然后什么时候处理呢?

这里要对内核软中断机制有一定的了解。简单说就是net_rx_action退出之后返回到__do_softirq, 这里面还会check是否有pending的中断。如果有并且没有超时(2ms),也没有超过最大restart次数(10次),也不需要resched,那么会重新调用net_rx_action重复之前的过程;

但是,如果不满足上述条件,将会退出软中断过程,

1)将net_rx_action的执行放在ksoftirqd

2)由于其他设备中断触发软中断后执行__do_softirq,间接执行net_rx_action

3)内核进程调用local_bh_enable中也会调用__do_softirq


pending=local_softirq_pending();

if(pending){

    if(time_before(jiffies,end)&&!need_resched()&&--max_restart)    

            goto restart;

    wakeup_softirqd();

}

所以补全之前的图:


IV.

关于NAPI的Tunning,可以看到主要有的参数有netdev_budget, napi_struct的budget,以及超时的2 jiffies。时间是hard-coded,除非你想改内核代码,前两个都可以在不同阶段tunning。最灵活的netdev_budget,可以run time改动,比如

 echo 600 > /proc/sys/net/core/netdev_budget

或者

sudo sysctl -w net.core.netdev_budget=600(更改 /etc/sysctl.conf当然也可以)

关于NAPI的debug或者说网络性能的debug,还有一个有意思统计值,就是softnet_data中的time_squeeze, 它记录了非正常退出的次数,对理解网络瓶颈有很大意义。


sd->time_squeeze++;


具体来说time_squeeze可以通过以下路径找到
cat /proc/net/softnet_stat 

打印出来的结果是一系列的值,对于kernel 3.13,来自net-procfs.c,softnet_seq_showtime_squeeze就是第三个值,请记住结果中的每一行代表一个CPU


seq_printf(seq,

"%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x\n",

sd->processed, sd->dropped, sd->time_squeeze,0,

0,0,0,0,/*was fastroute*/

sd->cpu_collision, sd->received_rps, flow_limit_count);


V.

最后我们不妨来考虑下面几种情况,到底是poll模式还是中断模式:

a. 中断频繁,数据量大

b. 中断不频繁,数据量大

c. 中断频繁,数据量小

d. 中断不频繁,数据量小

d. 中断不频繁,大包小包都有,就是数据量波动

欢迎讨论:bmebob_zhao@163.com

以上.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 背景 2017年年初以来,随着Redis产品的用户量越来越大,接入服务越来越多,再加上美团点评Memcache和R...
    SithCait阅读 699评论 0 0
  • 本来想继续写 socket 实现,发现网络栈是一个整体,不搞懂网卡与内核的交互,就缺少了最重要的一环。参考内核 4...
    董泽润阅读 5,691评论 0 5
  • 进程 创建 创建进程用fork()函数。fork()为子进程创建新的地址空间并且拷贝页表。子进程的虚拟地址空间...
    梅花怒阅读 1,873评论 0 7
  • 儿子今天考试,一早7点就起床了,没有了往日的磨蹭。吃过早饭送他下楼,他背着书包在我前面蹦蹦跳跳,穿着准备过年新...
    远致阅读 359评论 0 0
  • 我自己喜欢听《突然好想你》,这首曾经红遍大街小巷的歌。 以前我从来不怕朋友突然的关心,只怕朋友跟我渐行渐远。后来你...
    3a87c8175f0a阅读 364评论 1 4