转载 - Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity)

2 minute read

背景

原文

http://www.vpsee.com/2010/07/load-balancing-with-irq-smp-affinity/

原文

硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。现在的服务器上动不动就是多 CPU 多核、多网卡、多硬盘,如果能让网卡中断独占1个 CPU (core)、磁盘 IO 中断独占1个 CPU 的话将会大大减轻单一 CPU 的负担、提高整体处理效率。VPSee 前天收到一位网友的邮件提到了 SMP IRQ Affinity,引发了今天的话题:D,以下操作在 SUN FIre X2100 M2 服务器+ 64位版本 CentOS 5.5 + Linux 2.6.18-194.3.1.el5 上执行。

什么是中断

中文教材上对 “中断” 的定义太生硬了,简单的说就是,每个硬件设备(如:硬盘、网卡等)都需要和 CPU 有某种形式的通信以便 CPU 及时知道发生了什么事情,这样 CPU 可能就会放下手中的事情去处理应急事件,硬件设备主动打扰 CPU 的现象就可称为硬件中断,就像你正在工作的时候受到 QQ 干扰一样,一次 QQ 摇头就可以被称为中断。

中断是一种比较好的 CPU 和硬件沟通的方式,还有一种方式叫做轮询(polling),就是让 CPU 定时对硬件状态进行查询然后做相应处理,就好像你每隔5分钟去检查一下 QQ 看看有没有人找你一样,这种方式是不是很浪费你(CPU)的时间?所以中断是硬件主动的方式,比轮询(CPU 主动)更有效一些。

好了,这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?这个很容易,不是每个 QQ 号码都不相同吗?同样的,系统上的每个硬件设备都会被分配一个 IRQ 号,通过这个唯一的 IRQ 号就能区别张三和李四了。

在计算机里,中断是一种电信号,由硬件产生,并直接送到中断控制器(如 8259A)上,然后再由中断控制器向 CPU 发送信号,CPU 检测到该信号后,就中断当前的工作转而去处理中断。然后,处理器会通知操作系统已经产生中断,这样操作系统就会对这个中断进行适当的处理。现在来看一下中断控制器,常见的中断控制器有两种:可编程中断控制器 8259A 和高级可编程中断控制器(APIC),中断控制器应该在大学的硬件接口和计算机体系结构的相关课程中都学过。传统的 8259A 只适合单 CPU 的情况,现在都是多 CPU 多核的 SMP 体系,所以为了充分利用 SMP 体系结构、把中断传递给系统上的每个 CPU 以便更好实现并行和提高性能,Intel 引入了高级可编程中断控制器(APIC)。

光有高级可编程中断控制器的硬件支持还不够,Linux 内核还必须能利用到这些硬件特质,所以只有 kernel 2.4 以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的 CPU 上,这个绑定技术被称为 SMP IRQ Affinity. 更多介绍请参看 Linux 内核源代码自带的文档:linux-2.6.31.8/Documentation/IRQ-affinity.txt

如何绑定中断处理CPU

先看看系统上的中断是怎么分配在 CPU 上的,很显然 CPU0 上处理的中断多一些:

# cat /proc/interrupts   
           CPU0       CPU1         
  0:  918926335          0    IO-APIC-edge  timer  
  1:          2          0    IO-APIC-edge  i8042  
  8:          0          0    IO-APIC-edge  rtc  
  9:          0          0   IO-APIC-level  acpi  
 12:          4          0    IO-APIC-edge  i8042  
 14:    8248017          0    IO-APIC-edge  ide0  
 50:        194          0   IO-APIC-level  ohci_hcd:usb2  
 58:      31673          0   IO-APIC-level  sata_nv  
 90:    1070374          0         PCI-MSI  eth0  
233:         10          0   IO-APIC-level  ehci_hcd:usb1  
NMI:       5077       2032   
LOC:  918809969  918809894   
ERR:          0  
MIS:          0  

即能看到中断号,也能看到每个CPU的中断处理次数。

为了不让 CPU0 很累怎么把部分中断转移到 CPU1 上呢?或者说如何把 eth0 网卡的中断转到 CPU1 上呢?先查看一下 IRQ 90 中断的 smp affinity,看看当前中断是怎么分配在不同 CPU 上的(ffffffff 意味着分配在所有可用 CPU 上):

# cat /proc/irq/90/smp_affinity   
7fffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff  

在进一步动手之前我们需要先停掉 IRQ 自动调节的服务进程,这样才能手动绑定 IRQ 到不同 CPU,否则自己手动绑定做的更改将会被自动调节进程给覆盖掉。如果想修改 IRQ 90 的中断处理,绑定到第2个 CPU(CPU1):

# /etc/init.d/irqbalance stop  
  
# echo "2" > /proc/irq/90/smp_affinity  

(上面的 echo “2” 是怎么来的?为什么是 ”2“?请参考本文末尾:计算 SMP IRQ Affinity)过段时间在看 /proc/interrupts,是不是 90:eth0 在 CPU1 上的中断增加了(145)、在 CPU0 上的中断没变?不断打印 /proc/interrupts 就会发现 eth0 在 CPU0 上的中断数始终保持不变,而在 CPU1 上的中断数是持续增加的,这正是我们想要的结果:

# cat /proc/interrupts   
           CPU0       CPU1         
  0:  922506515          0    IO-APIC-edge  timer  
  1:          2          0    IO-APIC-edge  i8042  
  8:          0          0    IO-APIC-edge  rtc  
  9:          0          0   IO-APIC-level  acpi  
 12:          4          0    IO-APIC-edge  i8042  
 14:    8280147          0    IO-APIC-edge  ide0  
 50:        194          0   IO-APIC-level  ohci_hcd:usb2  
 58:      31907          0   IO-APIC-level  sata_nv  
 90:    1073399        145         PCI-MSI  eth0  
233:         10          0   IO-APIC-level  ehci_hcd:usb1  
NMI:       5093       2043   
LOC:  922389696  922389621   
ERR:          0  
MIS:          0  

有什么用

在网络非常 heavy 的情况下,对于文件服务器、高流量 Web 服务器这样的应用来说,把不同的网卡 IRQ 均衡绑定到不同的 CPU 上将会减轻某个 CPU 的负担,提高多个 CPU 整体处理中断的能力;对于数据库服务器这样的应用来说,把磁盘控制器绑到一个 CPU、把网卡绑定到另一个 CPU 将会提高数据库的响应时间、优化性能。合理的根据自己的生产环境和应用的特点来平衡 IRQ 中断有助于提高系统的整体吞吐能力和性能。

VPSee 经常收到网友来信问到如何优化 Linux、优化 VPS、这个问题不太好回答,要记住的是性能优化是一个过程而不是结果,不是看了些文档改了改参数就叫优化了,后面还需要大量的测试、监测以及持续的观察和改进。

其他

把irqbalance 停掉會不會有其他問題出玩?

不会有什么严重问题,但是没有 irqbalance 也没有合理的做手动 irq 绑定的话会有性能问题。手动 irq 只推荐给很 heavy、很特殊的情况,比如带多网卡多硬盘的网络存储服务器,一般机器一般应用还是用 irqbalance 省心。

事实上以前 Linux 是不推荐使用 irqbalance 的,原因在于当时的 irqbalance 实现很简单,没有什么优化作用,后来的 irqbalance (cache topology + power aware) 有了很大改进,在多核上表现良好。

计算 SMP IRQ Affinity

“echo 2 > /proc/irq/90/smp_affinity” 中的 ”2“ 是怎么来的,这其实是个二进制数字,代表 00000010,00000001 代表 CPU0 的话,00000010 就代表 CPU1, “echo 2 > /proc/irq/90/smp_affinity” 的意思就是说把 90 中断绑定到 00000010(CPU1)上。所以各个 CPU 用二进制和十六进制表示就是:

               Binary       Hex   
    CPU 0    00000001         1   
    CPU 1    00000010         2  
    CPU 2    00000100         4  
    CPU 3    00001000         8  

如果我想把 IRQ 绑定到 CPU2 上就是 00000100=4:

# echo "4" > /proc/irq/90/smp_affinity  

如果我想把 IRQ 同时平衡到 CPU0 和 CPU2 上就是 00000001+00000100=00000101=5

# echo "5" > /proc/irq/90/smp_affinity  

需要注意的是,在手动绑定 IRQ 到 CPU 之前需要先停掉 irqbalance 这个服务,irqbalance 是个服务进程、是用来自动绑定和平衡 IRQ 的:

# /etc/init.d/irqbalance stop  

还有一个限制就是,IO-APIC 有两种工作模式:

logic 和 physical,

在 logic 模式下 IO-APIC 可以同时分布同一种 IO 中断到8颗 CPU (core) 上(受到 bitmask 寄存器的限制,因为 bitmask 只有8位长。);

在 physical 模式下不能同时分布同一中断到不同 CPU 上,比如,不能让 eth0 中断同时由 CPU0 和 CPU1 处理,这个时候只能定位 eth0 到 CPU0、eth1 到 CPU1,也就是说 eth0 中断不能像 logic 模式那样可以同时由多个 CPU 处理。

下面给大家提供一个计算小脚本值提供中断在单CPU上

#!/bin/bash  
#Author Jiaion MSN:Jiaion@msn.com  
[ $# -ne 1 ] && echo$1 is Cpu core number’ && exit 1  
  
CCN=$1  
echo “Print eth0 affinity”  
for((i=0; i<${CCN}; i++))  
do  
echo ==============================  
echo "Cpu Core $i is affinity"  
((affinity=(1<<i)))  
echo "obase=16;${affinity}" | bc  
echo ==============================  
done  

Flag Counter

digoal’s 大量PostgreSQL文章入口