RPS/RFS/RSS/XPS

1620阅读 0评论2016-01-07 neptuntiansea
分类:LINUX

转自:http://blog.chinaunix.net/uid-20695170-id-3036641.html

网络协议栈的缩放(Scaling in the Linux Networking Stack)

简介

============

这个文档介绍了Linux网络协议栈中一系列互补的技术,用来增加多处理器系统的并行性和改善性能。包括:

RSS: Receive Side Scaling (接收侧的缩放) 
RPS: Receive Packet Steering (接收端包的控制) 
RFS: Receive Flow Steering (接收端流的控制) 
Accelerated Receive Flow Steering (加速的接收端流的控制) 
XPS: Transmit Packet Steering(发送端包的控制)


RSS: Receive Side Scaling 

=========================


当代的NICs支持多个接收和传输队列,即多队列。接收的时候,一个网卡能够发送不同的包到不同的队列,为了在不同的CPU之间分散处理。NIC针对每一个包,通过一个过滤器来指定这个包属于少数几个流中的一个流。每个流中的数据包被控制在一个单独的接收队列中,而队列轮回的被CPU进行处理。这种机制就叫做RSS。RSS的目标和其他控制技术目的都是为了增加性能。多队列也可以被用于流量优先控制,但那不是这些技术的目的。

 RSS中的过滤器是一个基于L3和L4层头部的hash函数,例如,基于IP地址和TCP端口的4元组的hash函数。最常见的RSS硬件实现中,使用了128个间接表,其中每个表存储一个队列号(注,网卡的队列数比较少,比如igb是8个,bnx2是5个)。针对某个包而言,使用这个包计算出的hash值(hash是Toeplitz算法)的低7位先确定间接表,再从间接表中的值访问队列。一些高级的NICs允许使用可编程的过滤器来控制包属于哪个队列。例如,绑定TCP端口80的webserver,数据包能被指向他们自己的队列。“n-tuple”过滤器可以通过ethtool的 --config-ntuple来配置。(注: 2.6.36开始引入!)

==== RSS Configuration

多队列网卡的驱动提供了一个内核模块参数,用来指定硬件队列个数。例如,bnx2x驱动使用的参数是num_queues. 如果设备支持足够多的队列,一个典型的RSS配置中,最好的是一个CPU一个接收队列。或者至少每个内存域一个接收队列,一个内存域包含一系列的CPU,并共享一个特殊的内存级别(L1,L2,NUMA节点等等)。RSS设备的间接表,在驱动初始化的时候被映射。默认的映射是队列均匀的发布在间接表中。但是,在运行的时候,使用ethtool命令 (--show-rxfh-indir and --set-rxfh-indir),间接表可以被查看,也可以被修改。修改间接表,可以给不同的队列不同比例的权重。

== RSS IRQ Configuration

每个接收队列有一个单独的IRQ,即中断号。NIC通过IRQ来通知CPU什么时候新的数据包到达了指定的队列。PCIe设备使用MSI-X来路由每个中断到CPU。有效的队列到IRQ的映射是由/proc/interrupts来制定的。默认,一个中断能被任何一个CPU处理。因为一个重要的包处理部分发生在接收中断处理函数函数中,在CPU中平分接收中断是有优点的。如果要手动的调节每个中断的亲和性,可以参考Documentation/IRQ-affinity.txt。一些系统会运行irqbalance服务,这个服务会动态的优化IRQ的亲和性,因此会覆盖任何手动设置。

== Suggested Configuration

当关注低延时或者接收中断处理称为瓶颈时,应该启用RSS。分担负载在不同的CPU之间,减少了队列长度。对于低延时的网络,最佳的设置是创建和CPU个数一样多的队列。最高效的配置是拥有最少的队列,并且没有队列溢出。这是因为,默认下中断聚合启用的情况下,中断的总数目户随着每个增加的队列而增加。每个cpu的负载可以使用mpstat工具来观测到。但是,注意,启用超线程的处理器,每一个超线程代笔了单独一个cpu。对于中断处理,在最初的测试中显示超线程并没有产生优势。所以,根据CPU的核个数,而不是逻辑cpu个数,来限制队列数目。

RPS: Receive Packet Steering 

============================

RPS,逻辑上是一种以软件的方式来实现RSS。在数据路径上,稍后被调用。介于RSS选择了队列和CPU(这个cpu会处理硬中断),RPS选择CPU来执行硬件中断处理之后的协议处理。通过把数据包放在目标CPU的backlog队列,并唤醒CPU来处理。RPS相比RSS有几个好处:

1) RPS能够被任何NIC使用。

2) 软件过滤器能够轻易的被添加,用来hash新的协议。

3) 它不会增加硬件设备的中断。尽管,引入了IPIs(inter-processor interrupts)。

当一个设备使用 netif_rx() 函数和netif_receive_skb()函数,(从网卡驱动)向网络协议栈传递数据包时,RPS在底半环境(通过软中断来实现的,在硬中断处理函数之后。)中被调用。这2个函数调用get_rps_cpu() 函数,来选择应该执行包的队列。决定目标CPU的第一步是基于包的地址和端口(有的协议是2元组,有的协议是4元组)来计算hash值。这个值与这个包的流保持一致。这个hash值要么是由硬件来提供的,

要么是由协议栈来计算的。厉害的硬件能够在包的接收描述符中传递hash值,这个值与RSS计算的值是相等的。这个hash值保存在skb->rx_hash中,并且这个值可以作为流的hash值可以被使用在栈的其他任何地方。每一个接收硬件队列有一个相关的CPU列表,RPS可以将包放到这个队列中进行处理。对于每一个接收到的包,指向这个列表的索引是通过流hash值对列表大小取模来计算的。被指向的CPU是处理 数据包的目标CPU,并且这个包被加到CPU的backlog队列的尾部。最底半处理的最后,IPI被发送到这个包所插到的那个CPU。IPI唤醒远程CPU来处理backlog队列,之后队列中数据包被发送到网络协议栈进行处理。

==== RPS Configuration

RPS要求内核编译了CONFIG_RPS选项(SMP上默认是打开的)。尽管编译到内核,直到被配置了才能启用。对于某个接收队列,RPS可以转发流量到哪个CPU,是由/sys/class/net//queues/rx-/rps_cpus来控制的。这个文件实现了CPU的位图。默认,当值是0,RPS是无效的,数据包是由中断的CPU来处理的。Documentation/IRQ-affinity.txt 解释了CPU是怎么由位图来设置的。

== Suggested Configuration

对于单个队列的设备,一个典型的RPS配置是设置rps_cpus指向与中断CPU属于相同内存域的CPU列表。如果NUMA位置不是一个问题,也可以设置所有的CPUs。如果高中断率,从cpu位图中排除高中断率的CPU是明智的,因为那个CPU已经执行了太多的工作。对于一个多队列的系统,如果RSS已经配置了,导致一个硬件接收队列已经映射到每一个CPU。那么RPS就是多余的和不必要的。如果只有很少的硬件中断队列(比CPU个数少),每个队列的rps_cpus 指向的CPU列表与这个队列的中断CPU共享相同的内存域,那RPS将会是有效的。


RFS: Receive Flow Steering

RPS只依靠hash来控制数据包,提供了好的负载平衡,但是它没有考虑应用程序的位置(注:这个位置是指程序在哪个cpu上执行)。RFS则考虑到了应用程序的位置。RFS的目标是通过指派应用线程正在运行的CPU来进行数据包处理,以此来增加数据缓存的命中率。RFS依靠RPS的机制插入数据包到指定CPU的backlog队列,并唤醒那个CPU来执行。

RFS中,数据包并不会直接的通过数据包的hash值被转发,但是hash值将会作为流查询表的索引。这个表映射数据流与处理这个流的CPU。这个数据流的hash值(就是这个流中的数据包的hash值)将被用来计算这个表的索引。流查询表的每条记录中所记录的CPU是上次处理数据流的CPU。如果记录中没有CPU,那么数据包将会使用RPS来处理。多个记录会指向相同的CPU。确实,当流很多而CPU很少时,很有可能一个应用线程处理多个不同hash值的数据流。

rps_sock_flow_table是一个全局的数据流表,这个表中包含了数据流渴望运行的CPU。这个CPU是当前正在用户层处理流的CPU。每个数据流表项的值是CPU号,这个会在调recvmsg,sendmsg (特别是inet_accept(), inet_recvmsg(), inet_sendmsg(), inet_sendpage() and tcp_splice_read()),被更新。(注:使用sock_rps_record_flow()来记录rps_sock_flow_table表中每个数据流表项的CPU号。

当调度器移动一个线程到一个新的CPU,而内核正在旧的CPU上处理接收到的数据包,这会导致数据包的乱序。为了避免这个, RFS使用了第二个数据流表来为每个数据流跟踪数据包:rps_dev_flow_table 是一个表,被指定到每个设备的每个硬件接收队列。每个表值存储了CPU号和一个计数值。这个CPU号表示了数据流中的数据包将被内核进一步处理的CPU。理想状态下,内核和用户处理发生正在同一个CPU上,由此在这两个表中这个CPU号是相同的。如果调度器已经迁移用户进程,而内核仍然有数据包被加到旧的CPU上,那么这两个值就不等了。

当这个流中的数据包最终被加到队列中, rps_dev_flow_table中的计数值记录了当前CPU的backlog队列的长度。每个backlog队列有一个队列头,当数据包从队列中出去后,这个队列头就会增加。队列尾部则等于队列头加上队列长度。换句话说,rps_dev_flow[i] 中的计数值记录了流i中的最后一个数据包,这个数据包已经添加到了目标CPU的backlog队列。当然,流i是由hash值选择的,并且多个数据流可以hash到同一个流i.

下面描述避免数据包乱序的技巧,当从get_rps_cpu()选择CPU来进行数据包处理,rps_sock_flow 和rps_dev_flow 将会进行比较。如果数据流的理想CPU(found in therps_sock_flow table)和当前CPU(found in the rps_dev_flow table)匹配,这个包将会加到这个CPU的backlog队列。如果他们不同,并且下面规则中任一个为真,则当前的CPU将会被更新,去匹配理想CPU。

- 当前CPU的队列头部大于等于rps_dev_flow[i]中记录的尾部计数值,这个计数值指向了CPU的队列的尾部。(说明当前cpu中没有多余的数据包未处理。)

- 当前CPU是未设置的。(等于NR_CPUS,RPS_NO_CPU=0xffff)

- 当前CPU是离线的。(注:应该是没有启用。)

(注:如果他们不同,并且当前CPU是有效的,则会继续用当前的CPU来处理。)检查了之后,数据包被发送到(可能)更新后的CPU.这些规则目标是当旧的CPU上没有接收到的数据包,才会移动数据流移动到一个新的CPU上。接收到的数据包能够在新的CPU切换后到达。

RFS Configuration

RFS需要内核编译CONFIG_RPS选项,直到明显的配置,RFS才起作用。全局数据流表(rps_sock_flow_table)的总数可以通过下面的参数来设置:

 /proc/sys/net/core/rps_sock_flow_entries

每个队列的数据流表总数可以通过下面的参数来设置:

 /sys/class/net//queues/rx-/rps_flow_cnt

== Suggested Configuration

针对每个接收队列启用RFS,上面的两个参数需要被设置。参数的值会被进位到最近的2的幂次方值。(参数的值是7,则实际有效值是8. 参数是值32,则实际值就是32.)建议的流计数依赖于期待的有效的连接数,这个值显著的小于连接总数。我们发现rps_sock_flow_entries设置成32768,在中等负载的服务器上,工作的很好。对于单队列设备,单队列的rps_flow_cnt值被配置成与 rps_sock_flow_entries相同。对于一个多队列设备,每个队列的rps_flow_cnt被配置成rps_sock_flow_entries/N, N是队列总数。例如,如果rps_sock_flow_entries设置成32768,并且有16个接收队列,每个队列的rps_flow_cnt最好被配置成2048.

Accelerated RFS(加速RFS)

加速RFS对于RFS而言,就像RSS对于RPS。 加速RFS是一个硬件加速的负载平衡机制。加速RFS基于应用线程正在运行的CPU,使用“soft state”来控制流。加速RFS应该比RFS执行的好,因为数据包直接发送到CPU,而消耗数据包的线程也在这个cpu上。目标CPU要么是和应用线程相同的CPU,要么至少是和应用线程在同一缓存层次的CPU(注:意思可能是共享同个cache的其他CPU)。

要启用加速RFS,网络协议栈调用ndo_rx_flow_steer驱动函数为数据包通讯理想的硬件队列,这个队列匹配数据流。当rps_dev_flow_table中的每个流被更新了,网络协议栈自动调用这个函数。驱动轮流地使用一种设备特定的方法指定NIC去控制数据包。

一个数据流的硬件队列是从rps_dev_flow_table的CPU记录中推断出来的。协议栈需要向NIC驱动咨询CPU到硬件队列的映射,因为这个映射是由NIC驱动来维护的。这个是自动从IRQ亲和性表(通过/proc/interrupts显示)生成的反转表。驱动可以使用cpu_rmap (“CPU affinity reverse map”) 内核库函数来填充这个映射。For each CPU, the corresponding queue in the map isset to be one whose processing CPU is closest in cache locality.(不知道怎么翻译了 :-0)

Accelerated RFS Configuration

加速RFS需要内核编译CONFIG_RFS_ACCEL,并且需要NIC设备和驱动都支持。并且要求ntuple过滤已经通过ethtool启用。CPU到队列的映射是自动从每个接收队列的IRQ亲和性配置推断出来的,所以无需格外的配置。

== Suggested Configuration

不管什么时候,只要你想用RFS并且NIC支持硬件加速,这个技术都需要被启用。

(支持这个的硬件有哪些??)

XPS: Transmit Packet Steering

XPS 是一种机制,用来智能的选择多队列设备的队列来发送数据包。为了达到这个目标,从CPU到硬件队列的映射需要被记录。这个映射的目标是专门地分配队列到一个CPU列表,这些CPU列表中的某个CPU来完成队列中的数据传输。这个有两点优势,第一点,设备队列上的锁竞争会被减少,因为只有很少的CPU对相同的队列进行竞争。(如果每个CPU只有自己的传输队列,锁的竞争就完全没有了。)第二点,传输时的缓存不命中的概率就减少,特别是持有sk_buff的数据缓存。

XPS通过设置使用队列进行传输的CPU位图,对每一个队列进行配置。相反的映射,从CPU到传输队列,是由网络设备计算并维护的。当传输数据流的第一个数据包时,函数get_xps_queue()被调用来选择一个队列。这个函数使用正在运行的CPU的ID号作为指向CPU-到-队列的查找表的key值。如果这个ID匹配一个单独的队列,那么这个队列被用来传输。如果多个队列被匹配,通过数据流的hash值作为key值来选择队列。

选择传输特殊数据流的队列被存储在相应的数据流的socket结构体(sk_tx_queue_mapping)。

这个传输队列被用来传输接下来的数据包,以防乱序(OOO)的包。这个选择也分担了为这个流中的所有数据包调用 get_xps_queues() 的开销。为了避免乱序的包,只有这个数据流中的某个包的skb->ooo_okay标志被设置了,这个数据流所使用的队列才能改变。这个标志表示数据流中没有待解决的数据包(注:被解决的数据包应该是指tcp_packets_in_flight()等于0。也就是说发送出去的数据包都被应答了),所以,这个传输队列才能安全的改变,而不会有产生乱序包的危险。传输层即L4层相应地有责任来设置ooo_okay标志位。例如,当一个连接的所有数据包被应答了,tcp才设置这个标志位。(UDP协议没有流的概念,所以没有必要设置这个标志。)

XPS Configuration

XPS要求内核编译了CONFIG_XPS选项(SMP上默认是打开的)。尽管编译到内核,直到被配置了才能启用。为了使用XPS,需要使用sysfs来配置传输队列的CPU位图:

/sys/class/net//queues/tx-/xps_cpus

== Suggested Configuration

对于只有一个传输队列的网络设置而言,XPS的配置没有任何效果,因为这种情况下没有选择。对于一个多队列系统,XPS更好的配置是每个CPU映射到一个队列中。如果有CPU一样多的队列,那么每个队列可以映射到每个CPU上,这就导致没有竞争的专一配对。如果队列比CPU少,共享指定队列的CPU最好是与处理传输硬中断(这个中断用来清理队列传输结束后的工作)的CPU共享缓存的CPU。

Further Information

RPS和RFS在内核2.6.35中被引入。XPS在2.6.38中被引入。原始的patches是由Tom Herbert
(therbert@google.com)来提交的。

加速RFS在2.6.35中被引入,原始的patches是由Ben Hutchings (bhutchings@solarflare.com)提交的。

Authors:
Tom Herbert (therbert@google.com)
Willem de Bruijn (willemb@google.com)


上一篇:LRU List基础
下一篇:没有了