中断简介
X86体系结构的计算机采用中断机制来协同处理器与其他设备的工作。当一个设备需要与处理器通信时,就会向处理器发出一个中断信号。例如敲击键盘时,键盘就会产生一个中断,通知操作系统有键被按下。在机器启动的时候,系统就已经识别了所有设备,并且也把相应的中断处理器加载到中断表中。所有的Linux操作系统都是基于中断驱动的,当我们在键盘上按下一个按键时,键盘就会对CPU说,一个键已经被按下。在这种情况下,键盘的IRQ线路中的电压就会发生一次变化,而这种电压的变化就是来自设备的请求,就相当于说这个设备有一个请求需要处理。
中断其实就是由硬件或软件所发送的一种称为IRQ(中断请求)的信号。中断允许让设备,如键盘,串口卡,并口等设备表明它们需要CPU。一旦CPU接收了中断请求,CPU就会暂时停止执行正在运行的程序,并且调用一个称为中断处理器或中断服务程序(interrupt service routine)的特定程序。
中断服务程序或中断处理器可以在中断向量表中找到,而这个中断向量表位于内存中的固定地址中。中断被CPU处理后,就会恢复执行之前被中断的程序,整个流程如下图所示。
由于中断会频繁发生,因此要求中断处理程序执行要快速。为了实现快速执行,必须要将一些繁重且不非常紧急的任务从中断处理程序中剥离出来,这一部分Linux中称为下半部,有三种方法处理下半部——软中断、tasklet和工作队列。
以网卡为例,当网卡收到数据包时会产生中断,通知内核有新数据包,然后内核调用中断处理程序进行响应,把数据包从网卡缓存拷贝到内存,因为网卡缓存大小有限,如果不及时拷出数据,后续数据包将会因为缓存溢出被丢弃,因此这一工作需要立即完成,这就是硬中断。剩下的处理和操作数据包的工作就会交给软中断。高负载的网卡是软中断产生的大户,很容易形成瓶颈。
为了解决中断处理程序执行过长和中断丢失的问题,Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部:
上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。这就是硬中断,特点是快速执行。
下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。这是软中断,特点是延迟执行。
硬中断和软中断的区别
硬中断
1)硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。
2)处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一种特殊的情况,就是在大型主机上是有硬件通道的,它可以在没有主CPU的支持下,可以同时处理多个中断。)。
3)硬中断可以直接中断CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理的进程,中断代码本身也可以被其他的硬中断中断。
4)对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其他的进程来运行。它的存在是为了让调度代码(或称为调度器)可以调度多任务。
软中断
软中断简介
1)软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
2)通常,软中断是一些对I/O的请求。这些请求会调用内核中可以调度I/O发生的程序。对于某些设备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。I/O可以在进程之间产生并且调度过程通常和磁盘I/O的方式是相同。
3)软中断仅与内核相联系。而内核主要负责对需要运行的任何其他的进程进行调度。一些内核允许设备驱动的一些部分存在于用户空间,并且当需要的时候内核也会调度这个进程去运行。
4)软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。
查看 /proc/softirqs
第一,要注意软中断的类型,也就是这个界面中第一列的内容。从第一列你可以看到,软中断包括了 10 个类别,分别对应不同的工作类型。比如 NET_RX 表示网络接收中断,而 NET_TX 表示网络发送中断。
1 | [root@linjx ~]# watch -d "cat /proc/softirqs |awk 'NR==1{print \"head\",\$1,\$2,\$3,\$4,\$5,\$6,\$7,\$8};NR>1{print \$1,\$2,\$3,\$4,\$5,\$6,\$7,\$8,\$9}' |column -t" |
第二,要注意同一种软中断在不同 CPU 上的分布情况,也就是同一行的内容。正常情况下,同一种中断在不同 CPU 上的累积次数应该差不多。比如这个界面中,NET_RX 在 CPU0 和 CPU1 上的中断次数基本是同一个数量级,相差不大。
软中断实际上是以内核线程的方式运行的,每个 CPU 都对应一个软中断内核线程,这个软中断内核线程就叫做ksoftirqd/CPU
编号。
1 | [root@linjx ~]# ps aux |grep softirq |grep -v grep |
网卡多队列
简介
多队列网卡是一种技术,最初是用来解决网络IO QoS (quality of service)问题的,后来随着网络IO的带宽的不断提升,单核CPU不能完全处满足网卡的需求,通过多队列网卡驱动的支持,将各个队列通过中断绑定到不同的核上,以满足网卡的需求。
查看
查看网卡是否支持多队列有2个方法,一种是使用lspci -vvv
来查看,另一种是使用ethtool -l
查看。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[root@localhost ~]# lspci -vvv 2>/dev/null|grep Eth -A16 |grep MSI-X
Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
[root@localhost ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 8 # 表示最多支持设置8个队列
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 8 #表示当前生效的是8个队列
可以使用ethtool -L eth0 combined 2
设置eth0当前使用2个队列
网卡相关其他参数的查看方法:
1 | # 查看网卡驱动 |
设置
smp_affinity设置
smp_affinity叫做 中断亲和力,要设置的话 ,得先查出网卡的中断号:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[root@localhost 148]# grep eth0 /proc/interrupts | grep -o "^ *[0-9]*" | xargs -i echo {}
147
148
149
150
151
152
153
154
155
[root@localhost 148]# cat /proc/irq/147/smp_affinity
000000,00000080
[root@localhost 148]# cat /proc/irq/147/smp_affinity_list
7
[root@localhost 148]# for n in `cat /proc/interrupts |grep eth0- |awk '{print $1}' |sed 's/://'`;do cpu=`cat /proc/irq/$n/smp_affinity_list`;echo -e "/proc/irq/$n/smp_affinity_list\t$cpu";done
/proc/irq/65/smp_affinity_list 15
/proc/irq/66/smp_affinity_list 16
/proc/irq/67/smp_affinity_list 17
/proc/irq/68/smp_affinity_list 18
/proc/irq/69/smp_affinity_list 19
/proc/irq/70/smp_affinity_list 20
/proc/irq/71/smp_affinity_list 21
/proc/irq/72/smp_affinity_list 22
可以看出中断号为147绑定到了CPU7上面了。其中smp_affinity是16进制表示;而smp_affinity_list是10进制的。
二者的转化关系如下:
1 | cup 二进制 smp_affinity_list(十进制) smp_affinity(十六进制) |
可以使用python的hex函数求出对应的16进制:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15>>> for i in range(1,11):print(i-1,hex(2**(i-1)))
...
0 0x1
1 0x2
2 0x4
3 0x8
4 0x10
5 0x20
6 0x40
7 0x80
8 0x100
9 0x200
# 也可以使用shell来处理
[root@localhost]# id=9;echo $((10**(id/4) * 2**(id%4)))
200
接下来就可以进行设置了。先停止irq的自动调节服务/etc/init.d/irqbalance stop
,然后如下设置:1
2
3
4
5
6[root@localhost 148]# echo 7 >smp_affinity;cat smp_affinity smp_affinity_list
000000,00000007
0-2 # 绑定CPU0 1 2,这边的7是16进制相加的结果
[root@localhost 148]# echo 401 >smp_affinity;cat smp_affinity smp_affinity_list
000000,00000401
0,10 # 绑定CPU0 10
从这里可以得出一个结论:绑定单个cpu只要写数字就行,如果是绑定多个cpu则用逗号隔开,如果是绑定连续CPU,则用-符号。注意:写入smp_affinity中的必须是16进制(不带0x标识),更新这个文件后,smp_affinity_list也会更新,这个文件里面是10进制。
taskset设置方法
使用taskset即可设置了。taskset -c 1,2,3 /etc/init.d/mysql start
或者taskset -cp 1,2,3 2345
把CPU#1 #2 #3分配给PID为2345的进程
附nic.sh脚本
1 |
|
或者:
1 | [root@localhost ~]# cat /usr/local/bin/set_irqbalance_for_intel.sh |
参考资料
- 中断解析:http://www.kerneltravel.net/journal/viii/01.htm
- https://www.cnblogs.com/bamanzi/p/linux-irq-and-cpu-affinity.html
- http://huoding.com/2013/10/30/296
- http://rfyiamcool.blog.51cto.com/1030776/1335700
- 多队列网卡CPU中断均衡:https://www.jianshu.com/p/1dfe40305bb5
- nic.sh脚本解释:http://www.blogjava.net/yongboy/archive/2015/01/30/422592.html
- 解决网卡丢包问题,以及丢包问题解决后系统网络还是慢的问题:https://hansedong.github.io/2019/01/17/13/