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/ == 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/ == 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 加速RFS在2.6.35中被引入,原始的patches是由Ben Hutchings (bhutchings@solarflare.com)提交的。 Authors: (完)
=============================
===================
(therbert@google.com)来提交的。
Tom Herbert (therbert@google.com)
Willem de Bruijn (willemb@google.com)