LINux网络的NAPI机制详解一
在查看NAPI机制的时候发现一篇介绍NAPI引入初衷的文章写的很好,通俗易懂,就想要分享下,重要的是博主还做了可以在他基础上任意修改,而并不用注明出处的声明,着实令我敬佩,不过还是附上原文链接!
http://blog.csdn.net/dog250/article/details/5302853
处理外部事件是cpu必须要做的事,因为cpu和外设的不平等性导致外设的事件被cpu 当作是外部事件,其实它们是平等的,只不过冯氏机器不这么认为罢了,既然要处理外部事件,那么就需要一定的方法,方法不止一种,大致有中断和轮询以及一种 混杂又复杂的方式,也就是DMA方式。中断是cpu被动处理的一种方式,也就是说cpu不知道何时中断,只要有了中断就会通知cpu,而cpu此时必须停 下一切来处理,而轮询是cpu主动查询并处理的过程,cpu隔一会查询一下外设看有没有事情可做。
我们看一下这两种方式,中断看似很高效,但是却会遗漏一些数据,避免遗漏的机制要么由硬件实现要么由上层的软件实现,而轮询就没有中断高效了,它会做很多 徒劳的操作,而且必须引入暂存机制,就是说由于cpu不可能在每次查询硬件的时候正好有事情可做,为了不使请求遗漏,随时到来的请求必须暂存在一个私有的 区域内,只要这些都做好了,轮询是不会造成请求遗漏的,中断在很多中断高频触发的时候会造成大量遗漏和竞争,毕竟只有一个cpu,同一个时间点只能有一个 请求被处理,而轮询由于是cpu分批打包处理请求的,因此不会遗漏。
以上的论述有点像我讨论过的inotify和rsync实现的文件同步,inotify的实现就是中断,很显然有遗漏,而rsync实现的就是轮询,显然 没有遗漏,cpu主动做的事情它自己最明白了,但是它要是被动应对就不会这么明白了,它只是按照规则应对罢了,丝毫不会存在任何策略。如果中断过于频繁也 是不好的,因为cpu必须处理中断,这会导致cpu没有时间做正经事,此时最好用轮询,但是外设活动很缓和的时候,用轮询就不合适了,因为询也是白询,此 时比较适合用中断,可是系统怎么知道何时外设活跃何时外设缓和呢?啊哈,可以用智能预测算法嘛,以历史值为依据!不,不能那样的,因为这是在内核,内核不 是秀算法的地方,我另外的文章强调过这一点。那么怎么办?好办,还是约定,就是将中断和轮询相结合,这就是linux网卡驱动中的NAPI的方式,它的设 计十分巧妙,就是在第一个包到来的时候中断,然后关闭中断开始轮询,等某一次轮询完毕后发现没有数据了,那么内核默认此次数据已经传输完毕,短时间内不会 再有数据了,那么停止轮询,重新开启中断,这样会减少很多次的中断,虽然某次轮询完毕发现没有数据并不能代表1ms以后不会再有数据,但是刚才说了,要想 使算法简单,必须做一个合理的约定,人性化的约定,如果说加上判定什么情况下百分之九十五的可能不需要轮询了并不是不可能,只是维护那个算法的开销太大, 它直接抵消了算法带来的优势。用人的思想考虑,如果一个饭店的服务员不停的从厨房接菜然后送到餐桌,注意是不停的,10秒一趟,但是突然隔了半分钟没有厨 房的人吆喝接菜,如果你是服务员,难道你还会去窗口等菜吗?反正我不会,我会蹲下来稍微休息一下,即使刚蹲下来就会有新活我也愿意赌一把,虽然输得可能性 很大很大。
如此一来,我们看一下NAPI解决了什么问题,第一,它限制了中断的数量,一旦有中断过来就停掉中断改为轮询,这样就不会造成cpu被频繁中断,第 二,cpu不会做无用功,就是所谓的无用的轮询,因为只有在中断来了才改为轮询,中断来了说明有事可做,看看NAPI将中断和轮询结合的是多么巧妙啊。以 往的实现中,在硬件网卡中断中将skb排入队,然后在软中断中出队并交由上层处理,一切配合的看起来那么好,可是在遇到突发快速小包传输的时候就会导致频 繁中断,因为是突发的包,因此不能用轮询,因为是快速小包,因此不适合用中断,最终二者巧妙结合,各取优势,优势互补,绝了!这个框架适合一切的网卡模 式,因此就将传统的网卡收发机制也纳入到了NAPI框架,很简单,就是用原来的逻辑实现dev的poll回调函数即可,至于传统的非NAPI方案,完全可 以用一个桩子代替。
cpu利用率和频繁的中断问题通过NAPI机制解决了,但是这又引入了一个新的问题,就是这可能造成cpu利用率的失衡,这个怎么理解呢?NAPI启动之 后,网卡的中断就会变得很少,要知道中断balance的目前实现是基于中断数量的均衡,它根本不管中断数量均衡之后引起的softirq导致的cpu使 用率是否均衡,softirq的均衡也是一样,比如一个是磁盘softirq,一个是网卡的NAPI的softirq,前者瞬间就可以完成但是来得频繁, 而后者要轮询网卡并且处理协议栈很耗时但是来得不频繁,可是balancer不管这么多,它只是觉得磁盘的softirq太多了而为了数量均衡会将更多的 softirq发布到softiqr少的cpu上,它根本不在乎这些更多的softirq是否会带来更高的cpu负载。NAPI削弱了中断/软中断均衡的 作用,毕竟它的主导在轮询,轮询会占用很多的处理器资源,而中断和软中断数量很少。中断或者软中断特别是软中断数量在cpu间的均衡可能造成各个cpu负 载的严重不均衡,因为各个硬中断几乎都是瞬间完成的,硬中断不能耽搁太久的,但是各个不同软中断的任务量缺是千差万别的,因此绝对不能按照数量来均衡软中 断,然而一般都是硬中断触发软中断,它们都在同一个cpu上,因此如果想简单的实现NAPI在多cpu上的cpu使用率均衡,那么必须重新实现硬件的负载 均衡机制,这样可以吗?不!因此这样会使得两个部分耦合过重,因此必须让硬中断的均衡和cpu的均衡解耦合,其实现在的内核就是这么做的,所以才会造成 cpu不均衡,硬件中断的均衡和cpu均衡的解耦合带来的好处就是我们可以对软中断均衡做文章,而硬中断的负载均衡还是用数量均衡实现,软中断彻底从硬件 中断中解放出来,不再是在处理硬中断的cpu上触发软中断,而是可以在任何cpu上触发软中断,由于不同软中断的任务量千差万别,因此我们定义一个软中断 的“权值”,然后按照不同软中断这个权值和数量的积的和来均衡软中断,这样的话,我想各个cpu的负载就均衡了,现在问题是,各个不同的软中断的“权值” 的计算问题,呵呵。累了,有时间再说。一个论坛上一哥们儿写了一个patch,很有创意,比我这里的软中断均衡的粒度要小得多,这个补丁不是均衡软中断, 而是将软中断进一步也分成了上下两部分,和cpu相关的上半部必须加急处理,这样不会对cpu造成太大负载,仍然用硬件中断均衡,因为硬件中断的cpu触 发软件中断,这部分不变,但是软中断的下半部就需要均衡了,该补丁为每一个cpu创立了一个工作队列,然后将ip_rcv 这种操作的cpu相关的工作放到软中断的上半部,其实就是从一个cpu的skb队列中抽取一个skb,然后将这个skb随机放到这些工作队列中进行处理, 和整个软中断均衡有何不同吗?大大不同。软中断均衡针对的是一个poll_list里面的所有的skb,而这哥们儿的补丁针对的是一个skb,粒度十分 小,但是没有测试,是不是太小了呢?这其实也是一个模式方法,逐步的将粒度精细化,类似将中断分成上半部和下半部的做法是放之四海而皆准的,这是一种哲 学,也是一种风格。
如果你说你没有见过linux的方式,那么只要你上过枯燥的计算机课或者读过枯燥的教科书或者你是天才你就知道一个叫做生产者/消费者的模型,它其实和 linux的中断的上半部和下半部很类似,上半部是生产者,只管将环境搭建好,数据准备好,然后触发下半部,其实就是唤醒消费者,这个思想在多线程并发中 很著名,而并发就是为了提高系统吞吐量,在SMP环境中也是为了并发,因此我们何尝不用用生产者/消费者模型呢?它也是一种低耦合的各司其职的模型。如果 你想不到NAPI的中断+轮询的方式,那么你听说过linux下怎样做文件同步的吗?rsync+inotify的方式听说过吗?如果没有就赶快 google一下吧。rsync+inotify其实就是中断+轮询,rsync是轮询,而inotify是中断,这个同步方案十分高效,保证只有在 inotify监控到文件变化的时候才开始轮询,平时就睡觉,inotify不再需要监控到具体的文件,因为它只负责告知事件,具体工作由rsync完 成,inotify只需要告诉一端文件变化了即可,那岂不是要全部同步了即使你只改了一个字符,别忘了rsync的算法,这就是另一篇文章了。所以不要再 觉得linux内核深不可测了,它的特点只有一个就是简单,比起用户应用那些复杂的算法,内核的算法一向简单易懂,其实内核的每一个机制,都可以在用户空 间找到原型的。
可是cpu对NAPI处理的均衡真的有意义吗?用户难道就不能忍受一个cpu占用100%而另一个0%吗?表面上看是那样的,但是如果考虑cache或者 切换代价的话就不一样了,性能因素不仅仅是cpu使用率还有别的,如果一件事的开销过大,即使cpu使用率再均衡也是划不来的。这就好像初学者用free 命令看内存时总是吓一大跳。特别是NAPI的网络数据包操作,比如TCP的IP包的分段重组问题,一旦乱序就要重传,这种情况下,一个linux主机如果只是作为一台路由器的话,那 么进入系统的一个TCP包的不同分段如果被不同的cpu处理并向一个网卡转发了,那么同步问题会很麻烦的,如果你不做同步处理,那么很可能后面的段被一个 cpu先发出去了,那么在真正的接收方接收到乱序的包后就会请求重发,这是不希望的,因此还是一个cpu串行处理好,这也许是TCP/IP协议栈的缺陷, 但是没有办法,协议是那样的,只能那样去应对。在大流量下,由于napi的缘故,网卡的中断被关闭了,此时那个第一次被中断的cpu正在poll这个网卡,因此所有的流量都会聚集到这个cpu上,这可能是一个设计的缺陷吧。