一般来说,我们一个网卡只有一个队列(queue
),如果在单队列网卡的网卡的情况下,所有收到的包从这个队列入, 内核从这个队列里取数据处理. 该队列其实是ring buffer
(环形队列), 内核如果取数据不及时, 则会存在丢包的情况。一个CPU
处理一个队列的数据, 这个叫中断。在单队列多核架构的虚拟机当中,默认是0号CPU
在处理,这样的话,如果流量特别大, 这个CPU
负载很高, 性能存在瓶颈。因此就有了多队列网卡的这个情况。即一个网卡有多个队列, 收到的包根据tcp四元组信息hash
后放入其中一个队列, 后面该链接的所有包都放入该队列。每个队列对应不同的中断, 使用irqbalance
将不同的中断绑定到不同的核。充分利用了多核并行处理特性. 提高了效率。
并不是所有的网卡都支持多队列的,如果你的网卡不支持多队列,那你输入以下命令会得到如下提示
[root@ccs ~]# ethtool -l eth0Channel parameters for eth0:Cannot get device channel parameters: Operation not supported
这是因为网卡驱动并没有实现ethtool 的 get_channels 方法。可能的原因包括:该网卡不支持调整 RX queue 数量,不支持 RSS/multiqueue,或者驱动没有更新来支持此功能。
正常的返回结果应该如下,我的服务器配置是4C8G, 如果返回信息中,两个 Combined 字段取值相同,则表示弹性网卡已开启支持多队列。
[root@ccs ~]# ethtool -l eth0Channel parameters for eth0:Pre-set maximums:RX: 0TX: 0Other: 0Combined: 4 # 表示最多支持设置4个队列Current hardware settings:RX: 0TX: 0Other: 0Combined: 4 #表示当前生效的是4个队列
OK,那么这里开始就表示你的网卡支持多队列啦,如果你的生效队列小于支持队列,那么你可以使用以下命令来开启多队列:
ethtool -L eth0 combined N
3.多队列网卡实现图示(这里使用双队列举例)
这里的eth0是网卡名称,N代表的事你想让网卡生效几个队列
4.中断与CPU的关系
普通单队列 ----------------------------- | queue || || ---------- ---------- | --------- | | packet | | packet ||---------->| CPU 0 || ---------- ---------- | --------- ----------------------------- 开启多网卡队列 ---------------------------- | queue || || ---------- ---------- | --------- | | packet | | packet | |---------> | CPU 0 || ---------- ---------- | --------- ---------------------------- --------- | CPU 1 | --------- --------- ---------------------------- | CPU 2 || queue | --------- | || ---------- ---------- | --------- | | packet | | packet | |---------> | CPU 3 || ---------- ---------- | --------- ----------------------------
第一列是中断号, 后面两列是对应CPU处理该中断的次数, virtio-input和 virtio-output为网卡队列的中断,我的服务器由于是云服务器,购买的时候就要求云厂商提供多队列网卡,所以云厂商的操作系统镜像是经过调试的,可以看到网卡的中断是均分给各个CPU的,如果你是刚刚开启的多队列网卡,那么你大部分的中断应该是被CPU0处理了。
[root@ccs ~]# cat /proc/interrupts | egrep 'CPU|virtio.*(input|output)'CPU0 cpu1 CPU2 CPU331: 15 0 0 117 PCI-MSI-edge virtio0-input.032: 1 0 0 0 PCI-MSI-edge virtio0-output.033: 11 0 125 0 PCI-MSI-edge virtio0-input.134: 1 0 0 0 PCI-MSI-edge virtio0-output.135: 15 135 0 0 PCI-MSI-edge virtio0-input.236: 1 0 0 0 PCI-MSI-edge virtio0-output.237: 151 0 0 0 PCI-MSI-edge virtio0-input.338: 1 0 0 0 PCI-MSI-edge virtio0-output.3
这个均分你也可以自己设置的,这个就涉及到中断跟CPU的亲和性了,下面就是你的服务器的中断
[root@ccs ~]# ls /proc/irq/0 1 10 11 12 13 14 15 2 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 4 5 6 7 8 9 default_smp_affinity
如果你要设置亲和性,以0号中断举例
[root@ccs ~]# cat /proc/irq/0/smp_affinity_list0-3
这样表示0号中断是绑定到了前**4个CPU(CPU0-3)**上面),你可以将其修改为专属于某个
CPU
的方式,直接修改值即可这里遍历几个我的中断当示例展示
5.软中断负载
[root@ccs ~]# for i in {24..30}; do echo -n "Interrupt $i is allowed on CPUs "; cat /proc/irq/$i/smp_affinity_list; doneInterrupt 24 is allowed on CPUs 1Interrupt 25 is allowed on CPUs 2Interrupt 26 is allowed on CPUs 0-3Interrupt 27 is allowed on CPUs 0-3Interrupt 28 is allowed on CPUs 3Interrupt 29 is allowed on CPUs 0Interrupt 30 is allowed on CPUs 0
top
进入交互式界面后, 按1显示所有cpu
的负载.si
是软中断的CPU
使用率. 如果高比如50%, 说明该CPU
忙于处理中断, 通常就是收发网络IO
6.拓展:RSS,RPS,RFS和XPS
top - 10:23:54 up 24 min, 1 user, load average: 1.45, 0.84, 1.06Tasks: 401 total, 1 running, 400 sleeping, 0 stopped, 0 zombie%Cpu0 : 11.8 us, 7.7 sy, 0.0 ni, 79.1 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st%Cpu1 : 10.7 us, 6.7 sy, 0.0 ni, 81.2 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st%Cpu2 : 11.1 us, 7.4 sy, 0.0 ni, 80.2 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st%Cpu3 : 12.1 us, 6.4 sy, 0.0 ni, 80.2 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 stKiB Mem : 8008632 total, 393060 free, 4095716 used, 3519856 buff/cacheKiB Swap: 0 total, 0 free, 0 used. 3457996 avail Mem
RSS(receive side scaling):网卡多队列
RPS(receive packet Steering):RSS的软件实现
RFS(receive flwo Steering): 基于flow的RPS
Accelerated RFS: RFS的硬件实现
XPS(transmit packet Steering):应用在发送方向
6.1 RSS
现在网卡都有多队列功能,在
MSIX
中断模式下,每个队列都可以使用单独的中断。在多核系统下,将每个队列绑定到单独的core上,即让单独的core
处理队列的中断,可通过下面的命令实现绑定,意思为让core 1
处理中断27,值得注意的是,cpu
列表最好和网卡在同一个numa
上。
echo 0x1 > /proc/irq/27/smp_affinity
在单队列模式下,网卡收到的包都会让queue 0处理,那多队列模式下,数据包如何分发呢?
网卡内部有Redirection table(表大小不同网卡有不同的实现),表中每项包含一个队列id,当网卡收到包后,会先解析报文出五元组等信息,然后根据这些信息(可以设置基于
ip
,tcp
,udp
等)计算出hash
值(hash
算法是固定的,但是使用的key
可以在初始化时指定),然后根据hash
低7位取模table size
,得到表的一项,取出此项包含的队列id
,此id即是数据包发往的队列。
a.
Parsed receive packet
解析数据包,获取五元组等信息b.
RSS hash
根据五元组的某些信息计算hash
值c.
Packet Descriptor
将hash值保存到接收描述符中,最终会保存到skb->hash
中,后续可以直接使用hash
值,比如RPS
查找cpu
时使用这个hash
值d.
7 LS bits
使用hash
值低7位索引redirection table
的一项,每项包含四位(所以最多支持16个队列)e.
RSS output index table
的指定项就是接收队列。那redirection table中每一项中的队列id是如何设置的呢? 在驱动初始化时,根据使能的队列个数,依次填充到每一项,达到队列最大值后,从0开始循环填充。比如使能了4个队列,则
table
的0-127项依次为:0,1,2,3,0,1,2,3...看下ixgbe中使用到的和
redirection table
相关的寄存器,使用32个IXGBE_RETA
寄存器,每个寄存器的0:3、11:8、19:16和27:24分别表示一个table
的entry
,而且是4位,所以使能RSS
时最多支持16个队列。6.2 RPS
在单队列网卡,多核系统上,或者多队列网卡,但是核数比队列多的情况下,可以使用
RPS
功能,将数据包分发到多个核上,使多核在软中断中处理数据包。这相当于是RSS
的软件实现。使能RPS也很简单,如下,将
cpu
列表写入队列对应的rps_cpus
即可。配置文件为
/sys/class/net/eth*/queues/rx*/rps_cpus
. 默认为0, 表示不启动RPS 如果要让该队列被CPU0、1
处理, 则设置echo "3" > /sys/class/net/eth*/queues/rx*/rps_cpus
, 3代表十六进制表示11, 即指CPU0
和CPU1
在开启多网卡队列RSS
时, 已经起到了均衡的作用。RPS
则可以在队列数小于CPU
数时, 进一步提升性能. 因为进一步利用所有CPU
6.3 RFS
从
RPS
选择cpu
方法可知,就是使用skb
的hash
随机选择一个cpu
,没有考虑到应用层运行在哪个cpu
上,如果执行软中断的cpu
和运行应用层的cpu
不是同一个cpu
,势必会降低cpu cache
命中率,降低性能。所以就有了
RFS
,原理是将运行应用的cpu
(为了高性能,一般都会将应用和cpu
绑定起来),保存到一个表中,在get_rps_cpu
时,从这个表中获取cpu
,即可保证执行软中断的cpu
和运行应用层的cpu
是同一个cpu
。这个表是
/proc/sys/net/core/rps_sock_flow_entries
。检查当前RPS
,RFS
开启情况:
# foriin$(ls -1 /sys/class/net/eth*/queues/rx*/rps_*);doecho -n "${i}: " ; cat ${i};done/sys/class/net/eth0/queues/rx-0/rps_cpus: 3/sys/class/net/eth0/queues/rx-0/rps_flow_cnt: 4096/sys/class/net/eth0/queues/rx-1/rps_cpus: 3/sys/class/net/eth0/queues/rx-1/rps_flow_cnt: 4096# cat /proc/sys/net/core/rps_sock_flow_entries8192
6.4 XPS
前面三个都是接收方向的,而
XPS
是针对发送方向的。网卡发送出去时,如果有多个发送队列,选择使用哪个队列。可通过如下命令设置,此命令表示运行在f指定的
cpu
上的应用调用socket
发送的数据会从网卡的tx-n
队列发送出去。echo f > /sys/class/net/<dev>/queues/tx-<n>/xps_cpus
虽然设置的是设备的
tx queue
对应的cpu
列表,但是转换到代码中保存的是每个cpu
可使用的queue
列表。因为查找xps_cpus
时,肯定是已知cpu id
,寻找从哪个tx queue
发送。选择
,tx queue
时,优先选择xps_cpu
指定的queue
,如果没有指定就使用skb hash
计算出来一个。当然也不是每个报文都得经过这个过程,只有socket
的第一个报文需要,选择出queue后,将此queue设置到sk->sk_tx_queue_mapping
,后续报文直接获取sk_tx_queue_mapping
即可。