SMP affinity and interrupt handling in Linux

本文是一篇译文,原文的地址为:http://www.alexonlinux.com/smp-affinity-and-proper-interrupt-handling-in-linux


Introduction

硬件中断是非常昂贵的,因为它会消耗大量的CPU处理能力。硬件和软件工程师已经做了很多工作试图改变这种状况,但硬件中断仍然会消耗大量CPU。

硬件中断对桌面系统的影响并不明显,具体信息可以通过/proc/interrupts文件来查看。这个文件中记录了所有的硬件设备以及它们在每个CPU上收到的中断数目。如果是在一个常规的桌面系统上,将会看到系统中处理的中断数目是相对较小的。即使是每秒处理百万数据包的超强服务器,一秒内也只需处理数万个中断。即便如此,通过恰当的方式降低中断所带来的CPU开销仍将毫无疑问的帮助提升系统性能。


But really, what can we do about interrupts?

事实上,还有很多事情可以做。许多Linux的发行版中都包含了能够明显改善这种情况的修改。一种有效的技术就是NAPI,它显著的降低了中断的数目和开销,因此现代服务器无需再忍受1Gbps的以太网链路。NAPI现在已经成为了内核的一部分。其他的技术还有像中断合并等。

在本文中,我将把注意力放在一个最有力的中断处理优化技术上。


SMP affinity

术语“SMP亲和性” 或 “处理器亲和性”有着广泛的含义,需要首先来解释一下。“亲和性”是指在多处理器系统中,将特定的任务放到特定的处理器上执行。例如,当进程Y运行在处理器X上时,它们之间就具有了亲缘关系,处理器会将进程的部分内存内容缓存在cache中,因此频繁的将进程调度到不同的处理器上是一种低效的方法。

考虑到中断的情况,“SMP 亲和性”是指特定的中断由哪个处理器来处理。与进程的情况不同,将中断绑定到特定的CPU上将会引起性能下降。下面就来说说为什么会这样。中断处理程序通常都非常小,因此它所占用的内存也很小,也就是说将中断保持在同一个CPU上不会改善缓存命中的情况。相反,过多的中断会使一个核心过载,而其他的核心却处在相对空闲的状态。调度程序却对此一无所知,它会假设我们的中断处理核心与其他核心一样繁忙,由此带来的结果就是你可能会面临一个瓶颈,这是由于某些进程或线程运行在了只有90%可用处理能力的核心上所造成的。

事情还可能会变得更加糟糕,因为在默认情况下core0会被用来处理所有中断。在一个繁忙的系统上,所有的中断加起来可能会消耗掉core0 30%的处理能力。假设所有核心都具有相同的处理能力,我们会发现我们的软件系统只能有效地利用CPU全部处理能力的70%。


Who's responsible

APIC or Advanced Programmable Interrupt Controller 已经集成到所有现代x86体系架构的系统中 --- 包括 SP(single-processor)和MP。APIC负责递交中断,并决定中断的去向(到哪个核心)。

默认情况下,APIC将所有中断递交到core0,这就是为什么在主流现代Linux操作系统上/proc/interrupts文件的输出内容看起来如下的原因:

         CPU0     CPU1     CPU2     CPU3
  0:   123357        0        0        0   IO-APIC-edge  timer
  8:        0        0        0        0   IO-APIC-edge  rtc
 11:        0        0        0        0  IO-APIC-level  acpi
169:        0        0        0        0  IO-APIC-level  uhci_hcd:usb1
177:        0        0        0        0  IO-APIC-level  qla2xxx
185:        0        0        0        0  IO-APIC-level  qla2xxx
193:    12252        0        0        0  IO-APIC-level  ioc0
209:        0        0        0        0  IO-APIC-level  uhci_hcd:usb2
217:      468        0        0        0  IO-APIC-level  eth0
225:      285        0        0        0  IO-APIC-level  eth1
NMI:      120       66       76       45
LOC:   123239   123220   123187   123065
ERR:        0
MIS:        0

看到了吗,CPU0处理了所有的硬件中断,这就是你将在一个错误配置了中断SMP亲和性的系统上看到的情形。


Simple solution for the problem

随着APIC的引入,这个问题已经有了多种解决方案。APIC包含了若干中断传递和指定模式,包括物理模式和逻辑模式,固定模式和低优先级模式,等等。一个重要的事实是它有能力将中断递送到任何一个核上,甚至在它们之间进行负载均衡。

APIC也有配置限制,它只能配置在前8个核心上。如果你有多于8个的核心,不要指望序号大于7的核心能够收到中断。

默认情况下,它的操作是physical/fixed的。也就是说,它会将特定的中断传递到特定的核心上(默认是core0),但你也可以方便的更改接收指定中断的核心号。

对于/proc/interrupts文件第一列中的每个IRQ号,在/proc/irq/目录下都有一个对应的子目录,其中包含了一个名为smp_affinity的文件,它可以用来修改处理这个中断的核心号。读这个文件可以获得一个由十六进制表示的掩码,其中的每一位代表一个核心。当特定位置位时,APIC会将这个中断传递到对应的核心。

让我们来看一个例子:

#
# cat /proc/interrupts
         CPU0     CPU1     CPU2     CPU3
  0: 19599546        0        0        0   IO-APIC-edge  timer
  8:        0        0        0        0   IO-APIC-edge  rtc
 11:        0        0        0        0  IO-APIC-level  acpi
169:        0        0        0        0  IO-APIC-level  uhci_hcd:usb1
177:        0        0        0        0  IO-APIC-level  qla2xxx
185:        0        0        0        0  IO-APIC-level  qla2xxx
193:    95337        0        0        0  IO-APIC-level  ioc0
209:        0        0        0        0  IO-APIC-level  uhci_hcd:usb2
217:   100778        0        0        0  IO-APIC-level  eth0
225:    56651        0        0        0  IO-APIC-level  eth1
NMI:      466      393      422      372
LOC: 19600453 19600434 19600401 19600279
ERR:        0
MIS:        0
#
#
# echo "2" > /proc/irq/217/smp_affinity
# cat /proc/interrupts
         CPU0     CPU1     CPU2     CPU3
  0: 19606722        0        0        0   IO-APIC-edge  timer
  8:        0        0        0        0   IO-APIC-edge  rtc
 11:        0        0        0        0  IO-APIC-level  acpi
169:        0        0        0        0  IO-APIC-level  uhci_hcd:usb1
177:        0        0        0        0  IO-APIC-level  qla2xxx
185:        0        0        0        0  IO-APIC-level  qla2xxx
193:    95349        0        0        0  IO-APIC-level  ioc0
209:        0        0        0        0  IO-APIC-level  uhci_hcd:usb2
217:   101027       49        0        0  IO-APIC-level  eth0
225:    56655        0        0        0  IO-APIC-level  eth1
NMI:      466      393      422      372
LOC: 19607629 19607610 19607577 19607455
ERR:        0
MIS:        0
#

可以看到,当我们输入了这个神奇的命令后,CPU1将代替CPU0,开始从eth0接收中断。用于完成这件事情的echo命令非常有趣,如果我们将“4”写入这个文件中,那么CPU2将代替CPU1来处理eth0的中断,就像之前已经提到的,它采用了位掩码的方式,每一位与一个单独的CPU关联。

那么写入“3”又会怎么样呢?理论上,这时APIC应该将中断传递到CPU0和CPU1上,不幸的是,这里的情况会更复杂一些。具体的行为依赖于APIC是否工作在 physical "destination mode" 以及 low priority "delivery mode"模式下。如果是的话,那么你不太可能看到CPU0处理所有中断,这是因为当APIC工作在 physical / low priority模式下的时候,会自动在前8个核心之间进行负载均衡。

因此,如果在你的系统上CPU0默认处理了全部的中断,这可能暗示了APIC的配置问题。


Ultimate solution

首先,不幸的是,别无选择,除非替换内核。配置APIC是内核的工作,如果我们想改变现状就只能在内核里修改它,那么唯一的问题就是,用什么来替换内核?

我在OpenSuSE 10.2(内核版本2.6.18)上进行了测试,使用OpenSuSE的默认内核配置(/proc/config.gz)安装2.6.24.3版本的内核(当时最新的版本)来修正这个问题。在这个内核上,事情看起来正确了:

# cat /proc/interrupts
         CPU0     CPU1     CPU2     CPU3
  0:   728895   728796   728624   728895  IO-APIC-edge     timer
  8:        0        0        0        0  IO-APIC-edge     rtc
 11:        0        0        0        0  IO-APIC-fasteoi  acpi
 16:        0        0        0        0  IO-APIC-fasteoi  uhci_hcd:usb1
 19:        0        0        0        0  IO-APIC-fasteoi  uhci_hcd:usb2
 24:    14090    14090    14327    14056  IO-APIC-fasteoi  ioc0
 49:        7        9        7        8  IO-APIC-fasteoi  qla2xxx
 50:        8       12       11       10  IO-APIC-fasteoi  qla2xxx
 77:     2849     2759     2841     2827  IO-APIC-fasteoi  eth0
 78:    25072    25138    24996    24980  IO-APIC-fasteoi  eth1
NMI:        0        0        0        0
LOC:  2915270  2915256  2915228  2915092
ERR:        0

看起来不错不是吗?所有的核心都在处理中断,工作效率也因此得到了最大化。

还有一个内核的配置选项横在我们的道路上,一旦移除掉它就能够在所有晚于2.6.10版本的内核上获得类似的效果。这个选项就是CONFIG_HOTPLUG_CPU,它增加了对CPU热插拔的支持。关掉这个选项,并正确的配置APIC。

实际上这很好理解,APIC被告知使用哪个CPU来接收中断,你需要增加一段代码来告知APIC如何处理CPU的移除事件 --- 而CPU的移除事件正是CONFIG_HOTPLUG_CPU允许你做的事,我假定这个功能在较早时被内核遗忘了,并在2.6.24.3版本之后被添加了进来。


Conclusion

我们已经看到,通过修改内核的配置可以获得实实在在的改进。在一个繁忙的系统中,通过一个小小的改动就可以成倍的提升服务器的生产力。

希望你能够认同这篇文章的内容,并将这些技术用到实际中去。


你可能感兴趣的:(SMP affinity and interrupt handling in Linux)