关于Linux cpu中断问题

作者:牧原

什么是中断?

当一个硬件(如磁盘控制器或者以太网卡), 需要打断CPU的工作时, 它就触发一个中断. 该中断通知CPU发生了某些事情并且CPU应该放下当前的工作去处理这个事情. 为了防止多个设置发送相同的中断, Linux设计了一套中断请求系统, 使得计算机系统中的每个设备被分配了各自的中断号, 以确保它的中断请求的唯一性.

从2.4 内核开始, Linux改进了分配特定中断到指定的处理器(或处理器组)的功能. 这被称为SMP IRQ affinity, 它可以控制系统如何响应各种硬件事件. 允许你限制或者重新分配服务器的工作负载, 从而让服务器更有效的工作.

以网卡中断为例,在没有设置SMP IRQ affinity时, 所有网卡中断都关联到CPU0, 这导致了CPU0负载过高,而无法有效快速的处理网络数据包,导致了瓶颈。

通过SMP IRQ affinity, 把网卡多个中断分配到多个CPU上,可以分散CPU压力,提高数据处理速度。但是smp_affinity要求网卡支持多队列,如果网卡支持多队列则设置才有作用,网卡有多队列,才会有多个中断号,这样就可以把不同的中断号分配到不同CPU上,这样中断号就能相对均匀的分配到不同的CPU上。

而单队列的网卡可以通过RPS/RFS来模拟多队列的情况,但是该效果并不如网卡本身多队列+开启RPSRFS 来的有效

什么是RPS/RFS

RPS(Receive Packet Steering)主要是把软中断的负载均衡到各个cpu,简单来说,是网卡驱动对每个流生成一个hash标识,这个HASH值得计算可以通过四元组来计算(SIP,SPORT,DIP,DPORT),然后由中断处理的地方根据这个hash标识分配到相应的CPU上去,这样就可以比较充分的发挥多核的能力了。通俗点来说就是在软件层面模拟实现硬件的多队列网卡功能,如果网卡本身支持多队列功能的话RPS就不会有任何的作用。该功能主要针对单队列网卡多CPU环境,如网卡支持多队列则可使用SMP irq affinity直接绑定硬中断。
关于Linux cpu中断问题

图1 只有RPS的情况下(来源网络)

由于RPS只是单纯把数据包均衡到不同的cpu,这个时候如果应用程序所在的cpu和软中断处理的cpu不是同一个,此时对于cpu cache的影响会很大,那么RFS(Receive flow steering)确保应用程序处理的cpu跟软中断处理的cpu是同一个,这样就充分利用cpu的cache,这两个补丁往往都是一起设置,来达到最好的优化效果, 主要是针对单队列网卡多CPU环境。
关于Linux cpu中断问题

图2:同时开启RPS/RFS后(来源网络)

rps_flow_cnt,rps_sock_flow_entries,参数的值会被进位到最近的2的幂次方值,对于单队列设备,单队列的rps_flow_cnt值被配置成与 rps_sock_flow_entries相同。
RFS依靠RPS的机制插入数据包到指定CPU的backlog队列,并唤醒那个CPU来执行

默认情况下,开启irqbalance是足够用的,但是对于一些对网络性能要求比较高的场景,手动绑定中断磨合是比较好的选择

开启irqbalance,会存在一些问题,比如:
a) 有时候计算出来的值不合理,导致CPU使用还是不均衡。
b) 在系统比较空闲IRQ处于 Power-save mode 时,irqbalance 会将中断集中分配给第一个 CPU,
以保证其它空闲 CPU 的睡眠时间,降低能耗。如果压力突然上升,可能会由于调整的滞后性带来性能问题。
c) 处理中断的CPU总是会变,导致了更多的context switch。
d)也存在一些情况,启动了irqbalance,但是并没有生效,没有真正去设置处理中断的cpu。

如何查看网卡的队列数

1,Combined代表队列个数,说明我的测试机有4个队列

# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:     0
TX:     0
Other:      0
Combined:   4
Current hardware settings:
RX:     0
TX:     0
Other:      0
Combined:   4

2,以ecs centos7.6为例,系统处理中断的记录在/proc/interrupts文件里面,默认这个文件记录比较多,影响查看,同时如果cpu核心也非常多的话,对于阅读的影响非常大

# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
  0:        141          0          0          0   IO-APIC-edge      timer
  1:         10          0          0          0   IO-APIC-edge      i8042
  4:        807          0          0          0   IO-APIC-edge      serial
  6:          3          0          0          0   IO-APIC-edge      floppy
  8:          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0   IO-APIC-fasteoi   acpi
 10:          0          0          0          0   IO-APIC-fasteoi   virtio3
 11:         22          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb1
 12:         15          0          0          0   IO-APIC-edge      i8042
 14:          0          0          0          0   IO-APIC-edge      ata_piix
 15:          0          0          0          0   IO-APIC-edge      ata_piix
 24:          0          0          0          0   PCI-MSI-edge      virtio1-config
 25:       4522          0          0       4911   PCI-MSI-edge      virtio1-req.0
 26:          0          0          0          0   PCI-MSI-edge      virtio2-config
 27:       1913          0          0          0   PCI-MSI-edge      virtio2-input.0
 28:          3        834          0          0   PCI-MSI-edge      virtio2-output.0
 29:          2          0       1557          0   PCI-MSI-edge      virtio2-input.1
 30:          2          0          0        187   PCI-MSI-edge      virtio2-output.1
 31:          0          0          0          0   PCI-MSI-edge      virtio0-config
 32:       1960          0          0          0   PCI-MSI-edge      virtio2-input.2
 33:          2        798          0          0   PCI-MSI-edge      virtio2-output.2
 34:         30          0          0          0   PCI-MSI-edge      virtio0-virtqueues
 35:          3          0        272          0   PCI-MSI-edge      virtio2-input.3
 36:          2          0          0        106   PCI-MSI-edge      virtio2-output.3
input0说明是cpu1(0号CPU)处理的网络中断            
阿里云ecs网络中断,如果是多个中断的话,还有input.1 input.2 input.3这种形式
......
PIW:          0          0          0          0   Posted-interrupt wakeup event

3,如果ecs的cpu核心非常多,那这个文件看起来就会比较费劲了,可使用下面的命令查看处理中断的核心

使用下面这个命令,即可将阿里云ecs处理中断的cpu找出来了(下面这个演示是8c 4个队列)
# for i in $(egrep "\-input."  /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
5
7
1
3
处理一下sar拷贝用
# for i in $(egrep "\-input."  /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done |tr -s '\n' ','
5,7,1,3,
#sar -P 5,7,1,3 1 每秒刷新一次cpu 序号为5,7,1,3核心的cpu使用率
# sar -P ALL 1 每秒刷新所有核心,用于少量CPU核心的监控,这样我们就可以知道处理慢的原因是不是因为队列不够导致的了
Linux 3.10.0-957.5.1.el7.x86_64 (iZwz98aynkjcxvtra0f375Z)   05/26/2020  _x86_64_    (4 CPU)
05:10:06 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
05:10:07 PM     all      5.63      0.00      3.58      1.02      0.00     89.77
05:10:07 PM       0      6.12      0.00      3.06      1.02      0.00     89.80
05:10:07 PM       1      5.10      0.00      5.10      0.00      0.00     89.80
05:10:07 PM       2      5.10      0.00      3.06      2.04      0.00     89.80
05:10:07 PM       3      5.10      0.00      4.08      1.02      0.00     89.80
05:10:07 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
05:10:08 PM     all      8.78      0.00     15.01      0.69      0.00     75.52
05:10:08 PM       0     10.00      0.00     16.36      0.91      0.00     72.73
05:10:08 PM       1      4.81      0.00     13.46      1.92      0.00     79.81
05:10:08 PM       2     10.91      0.00     15.45      0.91      0.00     72.73
05:10:08 PM       3      9.09      0.00     14.55      0.00      0.00     76.36
sar 小技巧
打印idle小于10的核心
sar -P 1,3,5,7 1 |tail -n+3|awk '$NF<10 {print $0}'
看所有核心是否有单核打满的把1357换成ALL即可
sar -P ALL 1 |tail -n+3|awk '$NF<10 {print $0}'
再贴一个4c8g规格的配置(ecs.c6.xlarge ),
可以看到4c也给了四个队列,但是默认设置的是在cpu0 和 2上处理中断
# grep -i "input" /proc/interrupts
 27:       1932          0          0          0   PCI-MSI-edge      virtio2-input.0
 29:          2          0       1627          0   PCI-MSI-edge      virtio2-input.1
 32:       1974          0          0          0   PCI-MSI-edge      virtio2-input.2
 35:          3          0        284          0   PCI-MSI-edge      virtio2-input.3
# for i in $(egrep "\-input."  /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
原因是cpu是超线程的,“每个vCPU绑定到一个物理CPU超线程”,所以即使是4个队列默认也在2个cpu核心上
# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1

4,关闭IRQbalance

# service irqbalance status
Redirecting to /bin/systemctl status irqbalance.service
● irqbalance.service - irqbalance daemon
   Loaded: loaded (/usr/lib/systemd/system/irqbalance.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Wed 2020-05-27 14:39:28 CST; 2s ago
  Process: 1832 ExecStart=/usr/sbin/irqbalance --foreground $IRQBALANCE_ARGS (code=exited, status=0/SUCCESS)
 Main PID: 1832 (code=exited, status=0/SUCCESS)
May 27 14:11:40 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Started irqbalance daemon.
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopping irqbalance daemon...
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopped irqbalance daemon.

5,手动设置RPS

5.1 手动设置之前我们需要先了解下面的文件(IRQ_number就是前面grep input拿到的序号)
进入/proc/irq/${IRQ_number}/,关注两个文件:smp_affinity和smp_affinity_list
smp_affinity是bitmask+16进制,
smp_affinity_list:这个文件更好理解,采用的是10进制,可读性高
改这两个任意一个文件,另一个文件会同步更改。
为了方便理解,咱们直接看十进制的文件smp_affinity_list即可

如果这一步没看明白,注意前面的 /proc/interrupts的输出
# for i in $(egrep "\-input."  /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
手动设置处理中断的CPU号码可以直接echo修改,下面就是将序号27的中断放到cpu0上处理,一般建议可以把cpu0空出来
# echo 0 >> /proc/irq/27/smp_affinity_list
# cat /proc/irq/27/smp_affinity_list
0
关于bitmask
“f” 是十六进制的值对应的 二进制是”1111”(可以理解为4c的配置设置为f的话,所有的cpu参与处理中断)
二进制中的每个位代表了服务器上的每个CPU. 一个简单的demo
  CPU序号  二进制  十六进制
   CPU 0   0001    1
   CPU 1   0010    2
   CPU 2   0100    4
   CPU 3   1000    8

5.2 需要对每块网卡每个队列分别进行设置。如对eth0的0号队列设置:
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
这里的设置方式和中断亲和力设置的方法是类似的。采用的是掩码的方式,但是这里通常要将所有的CPU设置进入,如:
4core,f
8core,ff
16core,ffff
32core,ffffffff
默认在0号cpu上

# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
0
# echo f >>/sys/class/net/eth0/queues/rx-0/rps_cpus
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
f

6,设置RFS的方式

需要设置两个地方:
6.1, 全局表:rps_sock_flow_table的条目数量。通过一个内核参数控制:

# sysctl -a |grep net.core.rps_sock_flow_entries
net.core.rps_sock_flow_entries = 0
# sysctl -w net.core.rps_sock_flow_entries=1024
net.core.rps_sock_flow_entries = 1024

6.2,每个网卡队列hash表的条目数:

#  cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
0
# echo 256 >> /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
#  cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
256

需要启动RFS,两者都需要设置。
建议机器上所有的网卡队列设置的rps_flow_cnt相加应该小于或者等于rps_sock_flow_entries。因为是4个队列,因此每个队列设置256,可以根据实际情况增大

某压测调整案例:

背景:iperf3 5个线程 1个1g打流量,抖动,如 4.6--5.2 波动较大,查看中断不均衡,做如下设置
1,设置RPS ,32c 16队列 设置ffffffff
2, RFS 65536 /16 = 4096
3, smp_affinity_list 1,3,5,7,9....31
依然不均衡,考虑到RFS 命中cache的问题,客户端增加到16线程,中断处理趋于稳定,但是流量依然有波动
4,关闭TSO后流量在7.9x--8.0x之间波动,趋于稳定

上一篇:10个方法助你轻松完成 Linux 系统恢复


下一篇:【Vmware】解决更换vcenter IP地址后报错