1.前言

一般来说,我们一个网卡只有一个队列(queue),如果在单队列网卡的网卡的情况下,所有收到的包从这个队列入, 内核从这个队列里取数据处理. 该队列其实是ring buffer(环形队列), 内核如果取数据不及时, 则会存在丢包的情况。一个CPU处理一个队列的数据, 这个叫中断。在单队列多核架构的虚拟机当中,默认是0号CPU在处理,这样的话,如果流量特别大, 这个CPU负载很高, 性能存在瓶颈。因此就有了多队列网卡的这个情况。即一个网卡有多个队列, 收到的包根据tcp四元组信息hash后放入其中一个队列, 后面该链接的所有包都放入该队列。每个队列对应不同的中断, 使用irqbalance将不同的中断绑定到不同的核。充分利用了多核并行处理特性. 提高了效率。

2.启用多队列网卡

并不是所有的网卡都支持多队列的,如果你的网卡不支持多队列,那你输入以下命令会得到如下提示

[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

这里的eth0是网卡名称,N代表的事你想让网卡生效几个队列

3.多队列网卡实现图示(这里使用双队列举例)

普通单队列 ----------------------------- | queue || || ---------- ---------- | --------- | | packet | | packet ||---------->| CPU 0 || ---------- ---------- | --------- ----------------------------- 开启多网卡队列 ---------------------------- | queue || || ---------- ---------- | --------- | | packet | | packet | |---------> | CPU 0 || ---------- ---------- | --------- ---------------------------- --------- | CPU 1 | --------- --------- ---------------------------- | CPU 2 || queue | --------- | || ---------- ---------- | --------- | | packet | | packet | |---------> | CPU 3 || ---------- ---------- | --------- ----------------------------

4.中断与CPU的关系

第一列是中断号, 后面两列是对应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的方式,直接修改值即可

这里遍历几个我的中断当示例展示

[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

5.软中断负载

top进入交互式界面后, 按1显示所有cpu的负载.si是软中断的CPU使用率. 如果高比如50%, 说明该CPU忙于处理中断, 通常就是收发网络IO

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

6.拓展:RSS,RPS,RFS和XPS
  • 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,当网卡收到包后,会先解析报文出五元组等信息,然后根据这些信息(可以设置基于iptcpudp等)计算出hash值(hash算法是固定的,但是使用的key可以在初始化时指定),然后根据hash7位取模table size,得到表的一项,取出此项包含的队列id,此id即是数据包发往的队列。

网卡队列是什么(浅浅聊一下网卡多队列)(1)

a. Parsed receive packet解析数据包,获取五元组等信息

b. RSS hash根据五元组的某些信息计算hash

c. Packet Descriptorhash值保存到接收描述符中,最终会保存到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个队列,则table0-127项依次为:0,1,2,3,0,1,2,3...

看下ixgbe中使用到的和redirection table相关的寄存器,使用32IXGBE_RETA寄存器,每个寄存器的0:3、11:8、19:1627:24分别表示一个tableentry,而且是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, 即指CPU0CPU1在开启多网卡队列RSS时, 已经起到了均衡的作用。RPS则可以在队列数小于CPU数时, 进一步提升性能. 因为进一步利用所有CPU

6.3 RFS

RPS选择cpu方法可知,就是使用skbhash随机选择一个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即可。

,