1、关于多队列网卡
通过lspci方式查看网卡信息,如果有MSI-X, Enable+ 并且Count > 1,则该网卡是多队列网卡,多队列网卡内部会有多个 Ring Buffer。
[root@localhost ~]# lspci -vvv | grep -A50 "Ethernet controller" | grep -E "Capabilities|Ethernet controller"
01:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
Capabilities: [40] Power Management version 3
Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
Capabilities: [a0] Express (v2) Endpoint, MSI 00
Capabilities: [e0] Vital Product Data
从以上信息可见,我们的这张网卡是支持多队列的。
2、网卡支持最大队列数及当前使用队列
我们可以通过ethtool命令查看网卡支持的最大队列数,
[root@localhost ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 63
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 40
由上可见,该网卡最多支持63个队列,当前使用了40个队列。为什么不开启63个队列呢,因为机器的CPU数没那么多,
[root@localhost ~]# cat /proc/cpuinfo | grep processor | wc -l
40
我们开启多队列的初衷就是为了利用多核。队列数和CPU相等,正好可以每个CPU处理一个队列,这样效率比较高。
3、修改网卡队列数
有时候网卡支持多队列却没有开启,那么就要手动设置网卡队列数,
ethtool -L eth0 combined 8
其中combined指的是网卡收发队列共用的情况,有些网卡可单独设置收发队列,
ethtool -L eth0 rx 8
ethtool -L eth0 tx 8
设置后可以在/sys/class/net/eth0/queues/目录下看到对应的队列,
[root@localhost ~]# cd /sys/class/net/eth0/queues/
[root@localhost queues]# ls
rx-0 rx-2 rx-4 rx-6 tx-0 tx-2 tx-4 tx-6
rx-1 rx-3 rx-5 rx-7 tx-1 tx-3 tx-5 tx-7
4、多队列网卡绑核
为了保证CPU均衡,也即是网卡中断能分配到各个CPU,我们通常会将网卡中断绑核,具体操作见——网卡中断均衡设置
5、单队列网卡
上面说的都是多队列网卡,那单队列的怎么搞呢,不能厚此薄彼吧。这时候就出现RPS和RFS了。简单来说就是在软件层面模拟多队列的情况,从而达到CPU均衡。
RPS(Receive Packet Steering)把软中断的负载均衡到各个cpu,是在单个CPU将数据从Ring Buffer取出来之后开始工作,网卡驱动通过四元组(SIP,SPORT,DIP,DPORT)生成一个hash值,然后根据这个hash值分配到对应的CPU上处理,从而发挥多核的能力。
但是还有个问题,由于RPS只是把数据包均衡到不同的cpu,但是收包的应用程序和软中断处理不一定是在同一个CPU,这样对于cpu cache的影响会很大。因此就出现RFS(Receive flow steering),它确保应用程序和软中断处理的cpu是同一个,从而能充分利用cpu的cache,这两个补丁往往都是一起设置,以达到最好的优化效果。
6、设置RPS
首先内核要开启CONFIG_RPS编译选项,然后设置需要将中断分配到哪些CPU,
/sys/class/net/<dev>/queues/rx-<n>/rps_cpus
比如,要将eth0上0号收包软中断均匀分配到64个CPU上(假设机器上有这么多CPU),那么可以如下操作,
echo "ffffffff,ffffffff" > /sys/class/net/eth0/queues/rx-0/rps_cpus
和多队列中断绑定规则类似,每个CPU用1位表示,因此1,2,4,8分别对应0-3号CPU,分配到这些CPU,相加就是15,即f。
如果只想分配到前32个CPU,则可以如下操作,
echo "00000000,ffffffff" > /sys/class/net/eth0/queues/rx-0/rps_cpus
7、设置RFS
上面我们说过RPS和RFS一般要配合使用,效果才最优,因此RFS同样需要开启CONFIG_RPS编译选项,同时设置每个队列的数据流表总数才能真正生效。
这里我们了解一下RFS的细节:
RFS的实现需要依赖两个表——全局socket流表(rps_sock_flow_table)和设备流表(rps_dev_flow_table)。全局socket流表记录的是每个流由上面RPS计算通过hash分配的CPU号,也就是期望的CPU号;设备流表存在于每个网络设备的每个接收队列,表中记录的是每个未完成流使用的CPU号,也就是当前流使用的CPU号。具体使用哪个CPU简单来说有以下规则,
- 如果两个表中记录的对应流使用的是同一个CPU号,就使用这个CPU
- 如果当前流使用的CPU未设置或者CPU处于离线状态,那就使用期望CPU表中的CPU号,也就是RPS计算而得的CPU号
- 如果两个表中对应流记录的CPU核不是同一个:
- a)如果同一流的前一段数据包未处理完,为了避免乱序,不更换CPU,继续使用当前流使用的CPU号
- b)如果同一流的前一段数据包已经处理完,那就可以使用期望CPU表中的CPU号
因此我们需要设置这两个表中记录的entry,对于全局socket流表(rps_sock_flow_table),该配置接口是
/proc/sys/net/core/rps_sock_flow_entries
而设备流表(rps_dev_flow_table)则通过以下接口设置,
/sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt
两者的关系如下,
rps_sock_flow_entries = rps_flow_cnt * N
其中,N就是队列数量。因此,对于单队列网卡,两个值是一样的。
8、XPS(Transmit Packet Steering)
上面说的都是关于接收队列,那对于发送队列呢,这就需要用到XPS了。
XPS通过创建CPU到网卡发送队列的对应关系,来保证处理发送软中断请求的CPU和向外发送数据包的CPU是同一个CPU,用来保证发送数据包时候的局部性。
对于发送队列到CPU的映射有两种选择:
1、使用CPU映射
这种方式是通过指定发送队列在某几个CPU上处理,通过减小分发的CPU范围来减少锁开销以及cache miss。最常见的就是1对1,和上面说到的接收软中断绑核类似,通过以下接口设置,
/sys/class/net/<dev>/queues/tx-<n>/xps_cpus
同样是bitmaps方式。
2、接收队列映射方式
这种方式基于接收队列的映射来选择CPU,也就是说让接收队列和发送队列在同一个CPU,或指定范围的几个CPU来处理。这种方式对于多线程一直收发包的系统效果比较明显,收发包队列处理在同一个CPU,不仅减少了对其他CPU的打断,同时提高应用处理效率,收完包后直接在同个CPU继续发包,从而减小CPU消耗,同时减小包的时延。
这种方式映射,可通过一下接口设置(不是所有网卡都支持),
/sys/class/net/<dev>/queues/tx-<n>/xps_rxqs
另外,XPS对于单发送队列网卡没有效果,这个可想而知。
参考资料:
1、https://www.kernel.org/doc/Documentation/networking/scaling.txt