网卡绑定内核
单进程、异步的I/O应该可以获得最优的通讯性能,但在现实中,我们常常发现这种模式达不到预期的效果,这可能是由于网卡在和应用程序争夺CPU资源。硬件中断的频繁发生是一件很消耗CPU资源的事情,在多CPU、多核心的条件下,如果有办法把大量硬件中断分配给特定的CPU核心进行处理,就能获得更好的性能。现在的服务器基本都是多CPU、多核心、多网卡、多硬盘,如果能分散和平衡各个中断,绑定特定的硬件中断到特定的CPU核心上,例如让网卡中断独占1个CPU 内核,磁盘I/O中断独占1个CPU核心,那么将会大大减轻单一CPU的负担,提高整体的处理效率。
1、什么是中断?
中文教材上对“中断”的定义太生硬了,简单的说就是:每个硬件设备(如硬盘、网卡等)都需要和CPU进行某种形式的通信,以便CPU及时知道发生了什么事情,这样CPU可能就会放下手中的事情去处理应急事件,硬件设备主动打扰CPU的现象就可以称为硬件中断。就像你正在工作的时候受到QQ干扰一样,一次QQ头像闪动就可以被理解为中断。
中断是一种比较好的CPU和硬件沟通的方式,还有一种方式叫做轮询(Polling),就是让CPU定时地对硬件状态进行查询然后做相应处理,就好像你每隔5分钟去检查一下QQ,看看有没有人找你一样,这种方式是不是很浪费时间呢?所以中断是硬件主动的方式,比轮询(CPU主动)更有效。这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?不同设备同时中断如何知道哪个中断是来自硬盘,哪个来自网卡呢?这个其实很容易,就好像每个QQ的号码都不相同,同样的,系统会为每个硬件设备分配一个IRQ号,通过这个唯一的IRQ号就能区别不同的硬件了。
在计算机里,中断是一种电信号,由硬件产生,并直接送到中断控制器上,然后再由中断控制器向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。
2、如何使用?
我们首先来了解两个基本命令:
- cat /proc/interrupts,查看系统上的中断情况,通常网卡的中断会被分配到CPU0上。
- cat cat /proc/cpuinfo,查看CPU信息,有多少CPU,有多少核心。
然后我们使用命令来查看系统上的中断是如何分配到CPU上的,很显然CPU0上处理的中断多一些:
# cat /proc/interrupts
CPU0 | CPU1 | |||
0: 1: 8: 9: 12: 14: 50: 58: 90: 233: NMI: LOC: ERR: MIS: |
918926335 2 0 0 4 8248017 194 31673 1070374 10 5077 918809969 0 0 |
0 0 0 0 0 0 0 0 0 0 2032 918809894 0 0 |
IO-APIC-edge IO-APIC-edge IO-APIC-edge IO-APIC-level IO-APIC-edge IO-APIC-edge IO-APIC-level IO-APIC-level PCI-MSI IO-APIC-level
|
timer i8042 rtc acpi i8042 ide0 ohci_hcd:usb2 sata_nv eth0 ehci_hcd:usb1
|
为了不让CPU0负载过大,如何将部分中断转移到CPU1上呢?或者说如何把eth0网卡的中断转到CPU1上呢?首先,我们需要查看一下IRQ 90(即eth0网卡的IRQ号)中断的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 > /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
还有一个限制是,IO-APIC有两种工作模式:logic和physical。在logic模式下,IO-APIC可以同时分布同一种中断到8个CPU核心上(受到bitmask寄存器的限制,因为bitmask只有8位);在physical模式下,不能同时分布同一种中断到不同的CPU核心上,例如不能让eth0中断同时由CPU0和CPU1处理,这个时候只能定位eth0到CPU0,定位eth1到CPU1,也就是说,eth0中断不能像logic模式那样可以同时由多个CPU核心进行处理。
过一段时间后,再查看/proc/interrupts信息,发现90: eth0在CPU1上的中断增加了145次,不断打印/proc/interrupts信息就会发现eth0在CPU0上的中断数始终保持不变,而在 CPU1上的中断数是持续增加的,这正是我们想要的结果:
# cat /proc/interrupts
CPU0 | CPU1 | |||
0: 1: 8: 9: 12: 14: 50: 58: 90: 233: NMI: LOC: ERR: MIS: |
922506515 2 0 0 4 8280147 194 31907 1073399 10 5093 922389696 0 0 |
0 0 0 0 0 0 0 0 145 0 2043 922389621 0 0 |
IO-APIC-edge IO-APIC-edge IO-APIC-edge IO-APIC-level IO-APIC-edge IO-APIC-edge IO-APIC-level IO-APIC-level PCI-MSI IO-APIC-level
|
timer i8042 rtc acpi i8042 ide0 ohci_hcd:usb2 sata_nv eth0 ehci_hcd:usb1
|
3、有什么用?
在网络负载非常重的情况下,对于文件服务器、高流量Web服务器这样的应用来说,把不同的网卡IRQ均衡地绑定到不同的CPU核心上,将会减轻单个CPU的负担,提高多CPU、多核心的整体处理中断的能力。对于数据库服务器这样的应用来说,把磁盘控制器绑到一个CPU核心,把网卡绑定到另一个CPU核心上,将会提高数据库的响应时间,达到优化性能的目的。合理地根据自己的生产环境和应用的特点来平衡IRQ中断有助于提高系统的整体吞吐能力和性能。