例行的Linux软中断分发机制与问题Linux的中断分为上下两半部,一般而言(事实确实也是如此),被中断的CPU执行中断处理函数,并在在本CPU上触发软中断(下半部),等硬中断处理返回后,软中断随即开中断在本CPU运行,或者wake up本CPU上的软中断内核线程来处理在硬中断中pending的软中断。
换句话说,Linux和同一个中断向量相关的中断上半部和软中断都是在同一个CPU上执行的,这个可以通过raise_softirq这个接口看出来。这种设计的逻辑是正确的,但是在某些不甚智能的硬件前提下,它工作得并不好。内核没有办法去控制软中断的分发,因此也就只能对硬中断的发射听之任之。这个分为两类情况:
1.硬件只能中断一个CPU按照上述逻辑,如果系统存在多个CPU核心,那么只能由一个CPU处理软中断了,这显然会造成系统负载在各个CPU间不均衡。
2.硬件盲目随机中断多个CPU注意”盲目“一词。这个是和主板以及总线相关的,和中断源关系并不大。因此具体中断哪个CPU和中断源的业务逻辑也无关联,比如主板和中断控制器并不是理会网卡的数据包内容,更不会根据数据包的元信息中断不同的CPU...即,中断源对中断哪个CPU这件事可以控制的东西几乎没有。为什么必须是中断源呢?因此只有它知道自己的业务逻辑,这又是一个端到端的设计方案问题。
因此,Linux关于软中断的调度,缺乏了一点可以控制的逻辑,少了一点灵活性,完全就是靠着硬件中断源中断的CPU来,而这方面,硬件中断源由于被中断控制器和总线与CPU隔离了,它们之间的配合并不好。因此,需要加一个软中断调度层来解决这个问题。
本文描述的并不是针对以上问题的一个通用的方案,因为它只是针对为网络数据包处理的,并且RPS在被google的人设计之初,其设计是高度定制化的,目的很单一,就是提高Linux服务器的性能。而我,将这个思路移植到了提高Linux路由器的性能上。
基于RPS的软中断分发优化在Linux转发优化那篇文章《Linux转发性能评估与优化(转发瓶颈分析与解决方案)》中,我尝试了网卡接收软中断的负载均衡分发,当时尝试了将该软中断再次分为上下半部:
上半部:用于skb在不同的CPU间分发。
下半部:用户skb的实际协议栈接收处理。
事实上,利用Linux 2.6.35以后加入的RPS的思想可能会有更好的做法,根本不用重新分割网络接收软中断。它基于以下的事实:
事实1:网卡很高端的情况如果网卡很高端,那么它一定支持硬件多队列特性以及多中断vector,这样的话,就可以直接绑定一个队列的中断到一个CPU核心,无需软中断重分发skb。
事实2:网卡很低档的情况如果网卡很低档,比如它不支持多队列,也不支持多个中断vector,且无法对中断进行负载均衡,那么也无需让软中断来分发,直接要驱动里面分发岂不更好(其实这样做真的不好)?事实上,即便支持单一中断vector的CPU间负载均衡,最好也要禁掉它,因为它会破坏CPU cache的亲和力。
为什么以上的两点事实不能利用中断中不能进行复杂耗时操作,不能由复杂计算。中断处理函数是设备相关的,一般不由框架来负责,而是由驱动程序自己负责。协议栈主框架只维护一个接口集,而驱动程序可以调用接口集内的API。你能保证驱动的编写人员可以正确利用RPS而不是误用它吗?
正确的做法就是将这一切机制隐藏起来,外部仅仅提供一套配置,你(驱动编写人员)可以开启它,关闭它,至于它怎么工作的,你不用关心。
因此,最终的方案还是跟我最初的一样,看来RPS也是这么个思路。修改软中断路径中NAPI poll回调!然而poll回调也是驱动维护的,因此就在数据包数据的公共路径上挂接一个HOOK,来负责RPS的处理。
为什么要禁掉低端网卡的CPU中断负载均衡答案似乎很简单,答案是:因为我们自己用软件可以做得更好!而基于简单硬件的单纯且愚蠢的盲目中断负载均衡可能会(几乎一定会)弄巧成拙!
这是为什么?因为简单低端网卡硬件不识别网络流,即它只能识别到这是一个数据包,而不能识别到数据包的元组信息。如果一个数据流的第一个数据包被分发到了CPU1,而第二个数据包分发到了CPU2,那么对于流的公共数据,比如nf_conntrack中记录的东西,CPU cache的利用率就会比较低,cache抖动会比较厉害。对于TCP流而言,可能还会因为TCP串行包并行处理的延迟不确定性导致数据包乱序。因此最直接的想法就是将属于一个流的所有数据包分发了一个CPU上。
我对原生RPS代码的修改要知道,Linux的RPS特性是google人员引入的,他们的目标在于提升服务器的处理效率。因此他们着重考虑了以下的信息:
哪个CPU在为这个数据流提供服务;
哪个CPU被接收了该流数据包的网卡所中断;
哪个CPU运行处理该流数据包的软中断。