Linux内核软RPS实现网络接收软中断的负载均衡分发(2)

理想情况,为了达到CPU cache的高效利用,上面的三个CPU应该是同一个CPU。而原生RPS实现就是这个目的。当然,为了这个目的,内核中不得不维护一个”流表“,里面记录了上面三类CPU信息。这个流表并不是真正的基于元组的流表,而是仅仅记录上述CPU信息的表。

而我的需求则不同,我侧重数据转发而不是本地处理。因此我的着重看的是:

哪个CPU被接收了该流数据包的网卡所中断;

哪个CPU运行处理该流数据包的软中断。

其实我并不看中哪个CPU调度发送数据包,发送线程只是从VOQ中调度一个skb,然后发送,它并不处理数据包,甚至都不会去访问数据包的内容(包括协议头),因此cache的利用率方面并不是发送线程首要考虑的。

因此相对于Linux作为服务器时关注哪个CPU为数据包所在的流提供服务,Linux作为路由器时哪个CPU数据发送逻辑可以忽略(虽然它也可以通过设置二级缓存接力[最后讲]来优化一点)。Linux作为路由器,所有的数据一定要快,一定尽可能简单,因为它没有Linux作为服务器运行时服务器处理的固有延迟-查询数据库,业务逻辑处理等,而这个服务处理的固有延迟相对网络处理处理延迟而言,要大得多,因此作为服务器而言,网络协议栈处理并不是瓶颈。服务器是什么?服务器是数据包的终点,在此,协议栈只是一个入口,一个基础设施。

在作为路由器运行时,网络协议栈处理延迟是唯一的延迟,因此要优化它!路由器是什么?路由器不是数据包的终点,路由器是数据包不得不经过,但是要尽可能快速离开的地方!

所以我并没有直接采用RPS的原生做法,而是将hash计算简化了,并且不再维护任何状态信息,只是计算一个hash:

[plain] view plaincopyprint?

target_cpu = my_hash(source_ip, destination_ip, l4proto, sport, dport) % NR_CPU;

target_cpu = my_hash(source_ip, destination_ip, l4proto, sport, dport) % NR_CPU;[my_hash只要将信息足够平均地进行散列即可!]

仅此而已。于是get_rps_cpu中就可以仅有上面的一句即可。

这里有一个复杂性要考虑,如果收到一个IP分片,且不是第一个,那么就取不到四层信息,因为可能会将它们和片头分发到不同的CPU处理,在IP层需要重组的时候,就会涉及到CPU之间的数据互访和同步问题,这个问题目前暂不考虑。

NET RX软中断负载均衡总体框架本节给出一个总体的框架,网卡很低端,假设如下:

不支持多队列;

不支持中断负载均衡;

只会中断CPU0。

它的框架如下图所示:

Linux内核软RPS实现网络接收软中断的负载均衡分发

CPU亲和接力优化本节稍微提一点关于输出处理线程的事,由于输出处理线程逻辑比较简单,就是执行调度策略然后有网卡发送skb,它并不会频繁touch数据包(请注意,由于采用了VOQ,数据包在放入VOQ的时候,它的二层信息就已经封装好了,部分可以采用分散/聚集IO的方式,如果不支持,只能memcpy了...),因此CPU cache对它的意义没有对接收已经协议栈处理线程的大。然而不管怎样,它还是要touch这个skb一次的,为了发送它,并且它还要touch输入网卡或者自己的VOQ,因此CPU cache如果与之亲和,势必会更好。

为了不让流水线单独的处理过长,造成延迟增加,我倾向于将输出逻辑放在一个单独的线程中,如果CPU核心够用,我还是倾向于将其绑在一个核心上,最好不要绑在和输入处理的核心同一个上。那么绑在哪个或者哪些上好呢?

我倾向于共享二级cache或者三级cache的CPU两个核心分别负责网络接收处理和网络发送调度处理。这就形成了一种输入输出的本地接力。按照主板构造和一般的CPU核心封装,可以用下图所示的建议:

Linux内核软RPS实现网络接收软中断的负载均衡分发

为什么我不分析代码实现第一,基于这样的事实,我并没有完全使用RPS的原生实现,而是对它进行了一些修正,我并没有进行复杂的hash运算,我放宽了一些约束,目的是使得计算更加迅速,无状态的东西根本不需要维护!

第二,我发现我逐渐看不懂我以前写的代码分析了,同时也很难看明白大批批的代码分析的书,我觉得很难找到对应的版本和补丁,但是基本思想却是完全一样的。因此我比较倾向于整理出事件被处理的流程,而不是单纯的去分析代码。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/16210.html