网络参数 RSS、RPS、RFS、aRFS 学习总结

最近学习,发现网络参数(包括内核参数、网卡参数)非常多,搞得人云里雾里,非常头大。因此打算在这里对学习到的东西做个总结,方便知识的梳理和以后的复习。

ReceiveSideScaling(RSS)

RSS是一个Linux网络协议栈中的调度机制。它的主要作用是将数据包路由到不同的接收队列,进而由该队列所对应的CPU进行处理,从而实现对数据包处理的负载均衡。

在硬件上,通常使用hash函数(一般是Toeplitzhash)和长度为128(2^7)的置换表来实现。具体做法是,首先将网络和传输层头部的一些信息(如地址端口四元组)通过hash函数计算出一个hash值,然后取该hash值的后7位。将这7位数作为索引,在置换表中寻找相应的值,该值即为相应的队列。

12
[hashvalue]:[queuenumber]

网卡的置换表可以通过命令ethtool--show-rxfh-indireth0来获取,例子如下:

可见一共有128条表项,完全均匀分配给了队列rx-0→rx-3。该表可通过命令ethtool—set-rxfh-indir进行配置。

上述机制实现了从数据包到队列的对应,为了真正实现负载均衡,我们还需要协调好从队列到CPU的对应关系。又因为每个队列对应一个中断(参考上一篇),所以我们可以通过中断号绑定的方式来实现队列和CPU的对应。关于此,请参考关于此,请参考理解CPU、中断、队列、进程的绑定关系-麋鹿博客(Elkblog)|一个分享知识和乐趣的地方。

RSS

技巧

该文档给出建议,想要实现低延时,应该配置CPU数量的队列(即每个CPU一个)。对此,笔者的理解是,如果队列数量少了,则可能会有CPU闲着,处理不够及时。队列数量多了也没用,因为CPU就那么多。

针对以高速率为目的的网络,文档建议使用尽可能少的队列,在这种配置下,不会出现接收队列因为一个CPU饱和而溢出的情况。因为在启用中断合并(如NAPI)的情况下,队列越多,中断数越多。

ReceivePacketSteering(RPS)

RPS逻辑上是RSS的软件实现,但又不完全一样。RSS可以为每个数据包选择接收队列,因此也就选择了相对应的执行中断处理的CPU;而在RPS中,中断由默认队列触发,中断处理函数结束之后,选择进行后续协议处理的CPU。

RPS

引用

RPS不会增加额外的硬件设备中断,但是引入了处理器间中断(IPI)。

—ScalingintheLinuxNetworkingStack—TheLinuxKerneldocumentation

RPS使用和RSS相同的hash计算方式,并且每个队列对应一个CPU列表。使用计算得到的hash值模上CPU列表的长度就得到了CPU号。当确定了新的CPU之后,数据包会被放置到相应CPU的backlog队列中。随后在新的CPU上触发一个IPI中断,告诉该CPU有RPS过来的新包需要其处理。

1
[CPUid]=[hashvalue]mod[len(CPUlist)]

在编译内核时通过设置CONFIG_RPS参数来设置是否支持RPS功能,系统默认为开。但是需要为想要使用RPS功能的队列配置上面提到的CPU列表。

RPS配置文件为/sys/class/net/device/queue/rx-queue/rps_cpus,其中device表示网络设备,如eth0,rx-queue表示接收队列,如rx-0。

该文件中存储的是一个十六进制的bitmap。每一个二进制比特代表一个CPU,最右边对应序号最小的CPU(即CPU0),最左边代表最大的。(参考上一篇)

每个队列的配置默认为0,即关闭RPS功能。如果要打开,用户可以根据规则自行设置。

技巧

如果网卡只支持单个队列,并且有多个CPU的的话,在NUMA系统中,一般将RPSCPU设置为在相同domain的CPU,非NUMA系统就无所谓了,因为设置为每一个CPU的性能都是一样的。

对于多队列网卡,一般系统不会同时启用RSS和RPS,因为这没什么好处。但也有例外,比如网卡所支持的队列数目少于CPU数,也就是每个CPU还分不到一个接收队列,那么使用RPS可能会使得CPU的任务分布更高效。

ReceiveFlowSteering(RFS)

以上机制很好地保证了数据包处理的负载均衡,可以将处理任务合理的分配到所有的CPU上。

但是,有时候我们还需要考虑其他的因素。比如,网卡收到了属于一个运行在CPU0上的进程的数据包,那么这些数据包被CPU0处理会比其他CPU处理更高效。

原因很简单直观,数据在CPU内传递比跨CPU传递要更节省时间。因此,我们希望数据包尽量能够被其所属的进程所在的CPU处理,至少能够被同属一个NUMA域的CPU处理。

RFS机制就是为了实现这一点。该机制利用了与RSS/RPS类似的hash和查找策略,即使用hash函数根据包头信息计算得到一个hash值,然后作为索引查表。但与之不同的是,RFS所查找的匹配表(rps_sock_flow_table)中存储的是数据包所属进程所在的CPU。

12
[hashvalue]:[CPUid]

如果能查找到有效的CPU,就按照与RPS相同的机制,将数据包入队到CPU对应的backlog队列中;如果查找不到,那么就直接按照RPS机制转发。

与RPS预先配置好的CPU列表不同,rps_sock_flow表是动态更新的。如果有数据包的收发操作,如inet_recvmsg(),inet_smsg(),inet_spage(),tcp_splice_read()等操作,则会插入新的值。类似的情况还发生在进程被调度到新的CPU的时候。这时候就需要更新匹配表中的值。如果原来的CPU队列上还有未处理完的数据包,那么就会发生乱序。

为了避免乱序,RFS使用了另一个表——rps_dev_flow表,每个网卡队列对应一个该表。该表的索引依旧是包头的hash值,每个索引对应两个字段:1)现在的CPU(也就是该数据包所属流已经把数据包放在其队列上等待其内核处理的CPU)号。2)当该流最后一个数据包到达后,该CPU的backlog队列的尾计数器值。

RFS

当选择处理数据包的CPU时,首先检查rps_sock_flow表和rps_dev_flow表中对应的CPU值是否相同。如果一样,说明进程还是运行在相同的CPU上,仍将数据包放在该CPU的队列上,没有问题。如果不一样,那么就看以下三个情况:

现在的CPU队列头计数器≥rps_dev_flow表中的队列尾计数器。

现在的CPU未被设置(≥nr_cpu_ids)。

现在的CPU下线了。

如果上面任意一条满足,就将现在的CPU更新为DesiredCPU(即rps_sock_flow表中的值)。否则,仍在原(现)CPU上执行。

直观的理解上面的判断机制,那就是:当进程切换CPU时,先判断原CPU队列上有没有未处理完的包,如果有就不切换;如果没有,就切换。

Linux内核编译可通过允许CONFIG_RPS来使能该功能,一般默认允许。但要是想让RFS真正工作,还需要进行配置上面提到的两个表,他们的配置路径分别为:

12
/proc/sys/net/core/rps_sock_flow_entries/sys/class/net/dev/queues/rx-n/rps_flow_cnt

技巧

该文档建议rps_sock_flow_entries的数量应该跟活跃流的数量相当,然后四舍五入取二的指数值。进一步,文档建议对于一般负载的服务器,值取为32768(2^15)效果比较好。对于rps_flow_cnt的取值应该参考rps_sock_flow_entries,因为前者是每个队列一个,后者是全局的,所以只要将rps_flow_cnt取为rps_sock_flow_entries/N就行,其中N为队列数。

关于该建议,本人的理解是:最好能够给每一个活跃的流都能分配一个表项,使其能够准确找到对应的CPU。

AcceleratedRFS

AcceleratedRFS之于RFS相当于RSS之于RPS。AcceleratedRFS在硬件上就可以选择正确的队列,随后触发该数据包所属流所在的CPU的中断。由此可见,如果想要在硬件上实现队列选择,我们需要一个从流到硬件队列的对应关系。

从上文可知,我们已经有了一个从流到CPU的映射关系,记录在rps_dev_flow表中。然后,我们也有CPU和硬件队列的关系,通过/proc/irq/irq_num/smp_affinity进行配置。

信息

回顾一下rps_dev_flow表中存储的信息,如果该表被更新,说明有某个进程被创建,或者进程连同协议处理完全迁移到了一个新的CPU。

aRFS

每当rps_dev_flow表中的条目被更新,网络协议栈就会调用驱动中的ndo_rx_flow_steer函数来更新流到硬件队列的对应关系。

AcceleratedRFS需要在编译阶段使能CONFIG_RFS_ACCEL,并且需要硬件和驱动的支持。此外,还需要使用ethtool设置ntuple过滤。其他的就不需要配置了。

技巧

AcceleratedRFS机制可以将数据包直接放在最终的CPU硬件队列上,所以性能应该是要比RFS高。因此,当硬件支持该选项,应该选择此机制。

总结

回顾一下这些机制之间的关系。首先是RSS/RPS,该机制在接收数据包的时候,通过手动配置,甚至是根据负载情况的自动配置(如irqbalance),来把数据包的处理负载均匀分配到不同的CPU上。RSS/RPS仅根据负载大小进行判断,但是这可能导致从协议栈处理到上层应用处理之间数据包的转移。

因此RFS/aRFS更进一步,在进行CPU选择的时候考虑上层应用所处位置,对每一个流都配置一个DesiredCPU。因此,在为数据包选择CPU的时候,会优先选择DesiredCPU。

参考

ScalingintheLinuxNetworkingStack—TheLinuxKerneldocumentation

ToeplitzHashAlgorithm-Wikipedia

8.7.ReceivePacketSteering(RPS)RedHatEnterpriseLinux6|RedHatCustomerPortal

SMPIRQaffinity—TheLinuxKerneldocumentation

LinuxNetworkScaling:ReceivingPackets()

版权声明:本站所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,不声明或保证其内容的正确性,如发现本站有涉嫌抄袭侵权/违法违规的内容。请举报,一经查实,本站将立刻删除。

相关推荐