storage R&D guy.
全部博文(1000)
分类: 服务器与存储
2014-07-02 21:11:52
网络协议栈的缩放(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/
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将会是有效的。