0%

网卡多队列总结(转)

中断是什么

中断使得硬件可以发松通知给处理器。例如敲击键盘时,键盘就会产生一个中断,通知操作系统有键被按下。

中断本质上是一种电信号,由硬件设备生成,送入中断控制器的输入引脚中。中断控制器(如8259A)是个简单的电子芯片,将多路中断管线,采用复用技术只通过一个和处理器连接的管线和处理器通信。当产生一个中断后,处理器会检测到一个电信号,中断自己的当前正在运行的程序,通知内核。内核调用一个称为中断处理程序(interrupt handler)或中断服务例程(interrupt service routine)的特定程序。中断处理程序或中断服务例程可以在中断向量表中找到,而这个中断向量表位于内存中的固定地址中。CPU处理中断后,就会恢复执行之前被中断的程序,整个流程如下图所示。

不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?这个很容易,系统上的每个硬件设备都会被分配一个 IRQ 号,通过这个唯一的IRQ号就能区别不同硬件设备了。

中断控制器

常见的中断控制器有两种:可编程中断控制器 8259A 和高级可编程中断控制器(APIC, Advanced Programmable Interrupt Controller),中断控制器应该在大学的硬件接口和计算机体系结构的相关课程中都学过。传统的 8259A 只适合单 CPU 的情况,现在都是多CPU多核的SMP体系,所以为了充分利用SMP体系结构、把中断传递给系统上的每个CPU 以便更好实现并行和提高性能,Intel 引入了高级可编程中断控制器(APIC)。

网卡收发包过程

由于中断会频繁发生,因此要求中断处理程序执行要快速。为了实现快速执行,必须要将一些繁重且不非常紧急的任务从中断处理程序中剥离出来,这一部分Linux中称为下半部,有三种方法处理下半部——软中断、tasklet和工作队列。

内核如何从网卡接受数据,传统的经典过程:

  1. 数据到达网卡;
  2. 网卡产生一个中断给内核;
  3. 内核使用I/O指令,从网卡I/O区域中去读取数据;

但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个问题是,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”

从这个描述本身可以看到,哪果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵……

OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:

  1. 首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
  2. 内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
  3. 网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
  4. 内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;

剩下的处理和操作数据包的工作就会交给软中断。高负载的网卡是软中断产生的大户,很容易形成瓶颈。

在相当长的时间内,网卡的中断都是通过CPU 0来处理的,造成CPU 0的压力很高、其他CPU相对空闲的情况。直到网卡多队列技术的出现,网卡多队列实际就是网卡的数据请求可以通过多个CPU处理。

多队列网卡的实现

多队列网卡硬件实现

常见的有Intel的82575、82576,Boardcom的57711等,下面以公司的服务器使用较多的Intel 82575网卡为例,分析一下多队列网卡的硬件的实现以及Linux内核软件的支持。

Intel 82575硬件逻辑图,有四个硬件队列。当收到报文时,通过hash包头的SIP、Sport、DIP、Dport四元组,将一条流总是收到相同的队列。同时触发与该队列绑定的中断。

RSS(Receive-Side Scaling, also known as multi-queue receive),是网卡的硬件特性,实现多队列,将不同的流分发到不同的CPU上,同一数据流始终在同一CPU上,避免TCP的顺序性和CPU的并行性发生冲突。

Linux kernel 2.6.21前网卡驱动的实现

Linux kernel从2.6.21之前不支持多队列特性,一个网卡只能申请一个中断号,因此同一个时刻只有一个核在处理网卡收到的包。如图2.1,协议栈通过NAPI轮询收取各个硬件queue中的报文到图2.2的net_device数据结构中,通过QDisc队列将报文发送到网卡。

Linux kernel 2.6.21后网卡驱动的实现

Linux kernel 2.6.21开始支持多队列特性,当网卡驱动加载时,通过获取的网卡型号,得到网卡的硬件queue的数量,并结合CPU核的数量,最终通过Sum=Min(网卡queue,CPU core)得出所要激活的网卡queue数量(Sum),并申请Sum个中断号,分配给激活的各个queue。

如图3.1,当某个queue收到报文时,触发相应的中断,收到中断的核,将该任务加入到协议栈负责收包的该核的NET_RX_SOFTIRQ队列中(NET_RX_SOFTIRQ在每个核上都有一个实例),在NET_RX_SOFTIRQ中,调用NAPI的收包接口,将报文收到CPU中如图3.2的有多个netdev_queue的net_device数据结构中。这样,CPU的各个核可以并发的收包,就不会因为一个核不能满足需求,导致网络IO性能下降。

但当CPU可以平行收包时,就会出现不同的核收取了同一个queue的报文,这就会产生报文乱序的问题,解决方法是将一个queue的中断绑定到唯一的一个核上去,从而避免了乱序的问题。

查看网卡是否支持多队列

查看网卡是否支持多队列,使用lspci -vvv命令,找到Ethernet controller项:

如果有MSI-X, Enable+ 并且Count > 1,则该网卡是多队列网卡。

Message Signaled Interrupts(MSI)是PCI规范的一个实现,可以突破CPU 256条interrupt的限制,使每个设备具有多个中断线变成可能,多队列网卡驱动给每个queue申请了MSI。MSI-X是MSI数组,实际应用场景中,MSI方式的中断对多核cpu的利用情况不佳,网卡中断全部落在某一个cpu上,即使设置cpu affinity也没有作用,而MSI-X中断方式可以自动在多个cpu上分担中断

然后可以查看是否打开了网卡多队列,使用命令cat /proc/interrupts,如果看到如下图信息表明多队列支持已经打开:

是不是某个CPU在一直忙着处理IRQ?

这个问题我们可以从 mpstat -P ALL 1 的输出中查明:里面的 %irq一列即说明了CPU忙于处理中断的时间占比

上面的例子中,第四个CPU有25.63%时间在忙于处理中断(这个数值还不算高,如果高达80%(而同时其它CPU这个数值很低)以上就说明有问题了),后面那个 intr/s 也说明了CPU每秒处理的中断数(从上面的数据也可以看出,其它几个CPU都不怎么处理中断)。

然后我们就要接着查另外一个问题:这个忙于处理中断的CPU都在处理哪个(些)中断?这要看/proc/interrupts文件

/proc/interrupts文件

这里记录的是自启动以来,每个CPU处理各类中断的数量(第一列是中断号,最后一列是对应的设备名)

对上面文件的输出,解释如下:

● 第一列表示IRQ号。

● 第二、三、四列表示相应的CPU核心被中断的次数。在上面的例子中,timer表示中断名称(为系统时钟)。1825291229表示CPU0被中断了1825291229次。i8042表示控制键盘和鼠标的键盘控制器。

● 对于像rtc(real time clock)这样的中断,CPU是不会被中断的。因为RTC存在于电子设备中,是用于追踪时间的。

● NMI和LOC是系统所使用的驱动,用户无法访问和配置。

IRQ号决定了需要被CPU处理的优先级,IRQ号越小意味着优先级越高。

例如,如果CPU同时接收了来自键盘和系统时钟的中断,那么CPU首先会服务于系统时钟,因为他的IRQ号是0。

● IRQ0 :系统时钟(不能改变)。

● IRQ1 :键盘控制器(不能改变)。

● IRQ3 :串口2的串口控制器(如有串口4,则其也使用这个中断)。

● IRQ4 :串口1的串口控制器(如有串口3,则其也使用这个中断)。

● IRQ5 :并口2和3或声卡。

● IRQ6 :软盘控制器。

● IRQ7 : 并口1,它被用于打印机或若是没有打印机,可以用于任何的并口。

而对于像操作杆(或称为游戏手柄)上的CPU,它并不会等待设备发送中断。因为操作杆主要用于游戏,操作杆的移动必须非常快,因此使用轮询的方式检测设备是否需要CPU的关注还是比较理想的。使用轮询方式的缺点是CPU就处于了忙等状态,因为CPU会不停的多次检查设备。但是需要注意的是在Linux中,这种处理信号的方式也是必不可少的。

最后确认每个队列是否绑定到不同的CPU核心上,cat /proc/interrupts查询到每个队列的中断号,对应的文件/proc/irq/${IRQ_NUM}/smp_affinity为中断号IRQ_NUM绑定的CPU核的情况。以十六进制表示,每一位代表一个CPU核:

(00000001)代表CPU0

(00000010)代表CPU1

(00000011)代表CPU0和CPU1

SMP是指”对称多处理器”,smp_affinity文件主要用于某个特定IRQ要绑定到哪个CPU核心上。在 /proc/irq/IRQ_NUMBER/目录下都有一个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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[root@192-168-152-52 ~]# cat /proc/irq/108/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
[root@192-168-152-52 ~]# cat /proc/irq/109/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000002
[root@192-168-152-52 ~]# cat /proc/irq/110/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000004
[root@192-168-152-52 ~]# cat /proc/irq/111/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000008
[root@192-168-152-52 ~]# cat /proc/irq/112/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000010
[root@192-168-152-52 ~]# cat /proc/irq/113/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000020
[root@192-168-152-52 ~]# cat /proc/irq/114/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000040
[root@192-168-152-52 ~]# cat /proc/irq/115/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000080
[root@192-168-152-52 ~]# cat /proc/irq/116/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000100
[root@192-168-152-52 ~]# cat /proc/irq/117/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000200
[root@192-168-152-52 ~]# cat /proc/irq/118/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
[root@192-168-152-52 ~]# cat /proc/irq/119/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000002
[root@192-168-152-52 ~]# cat /proc/irq/120/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000004
[root@192-168-152-52 ~]# cat /proc/irq/121/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000008
[root@192-168-152-52 ~]# cat /proc/irq/122/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000010
[root@192-168-152-52 ~]# cat /proc/irq/123/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000020
[root@192-168-152-52 ~]# cat /proc/irq/124/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000040
[root@192-168-152-52 ~]# cat /proc/irq/125/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000080
[root@192-168-152-52 ~]# cat /proc/irq/126/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000100
[root@192-168-152-52 ~]# cat /proc/irq/127/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000200
[root@192-168-152-52 ~]# cat /proc/irq/128/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
[root@192-168-152-52 ~]# cat /proc/irq/129/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000002
[root@192-168-152-52 ~]# cat /proc/irq/130/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000004
[root@192-168-152-52 ~]# cat /proc/irq/131/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,00000008
[root@192-168-152-52 ~]# cat /proc/irq/132/smp_affinity
0000,00000000,00000000,00000000,00000000,00000000,00000000,0003f03f

上面说明网卡队列绑定在CPU0~CPU0上。我们可以通过手动改变smp_affinity文件中的值来将IRQ绑定到指定的CPU核心上,或者启用irqbalance服务来自动绑定IRQ到CPU核心上。

IRQ Balance

Irqbalance是一个Linux的实用程序,它主要是用于分发中断请求到CPU核心上,有助于性能的提升。它的目的是寻求省电和性能优化之间的平衡。你可以使用yum进行安装(CentOS系统一般默认安装):

1
2
yum -y install irqbalance
/etc/init.d/irqbalance start

Irqbalance对于包含多个核心的系统来说是非常有用的,因为通常中断只被第一个CPU核心服务。

手动绑定亲和性

● 动态监控CPU中断情况,观察中断变化

1
watch -d -n 1 cat /proc/interrupts

● 查看网卡中断相关信息

1
cat /proc/interrupts | grep -E “eth|CPU”

● 网卡亲和性设置
修改proc/irq/irq_number/smp_affinity之前,先停掉irq自动调节服务,不然修改的值就会被覆盖。

1
/etc/init.d/irqbalance stop

通过查看网卡中断相关信息,得到网卡中断为19

1
2
3
4
5
[root@master ~]# cd /proc/irq/19
[root@master 19]# cat smp_affinity
00000000,00000000,00000000,00000001
[root@master 19]# cat smp_affinity_list
0

修改值,将19号中断绑定在cpu2上:

1
2
3
4
5
[root@master 19]# echo 4 > smp_affinity
[root@master 19]# cat smp_affinity
00000000,00000000,00000000,00000004
[root@master 19]# cat smp_affinity_list
2

如果是要将网卡中断绑定在cpu0和cpu2上怎么做了?请先参照上文中的CPU列表。cpu0和2的十六进制值分别为1,4。那么如果要同时绑定在cpu0和cpu2上,则十六进制值为5,如下:

1
2
3
4
5
[root@master 19]# echo 5 > smp_affinity
[root@master 19]# cat smp_affinity
00000000,00000000,00000000,00000005
[root@master 19]# cat smp_affinity_list
0,2

taskset为系统进程PID设置CPU亲和性

查看某个进程的CPU亲和性

1
2
# taskset -p 30011
pid 30011's current affinity mask: ff

设置某个进程的CPU亲和性

1
2
3
# taskset -p 1 30011
pid 30011's current affinity mask: ff
pid 30011's new affinity mask: 1

使用-c选项可以将一个进程对应到多个CPU上去

1
2
3
4
5
6
7
# taskset -p -c 1,3 30011
pid 30011's current affinity list: 0
pid 30011's new affinity list: 1,3

# taskset -p -c 1-7 30011
pid 30011's current affinity list: 1,3
pid 30011's new affinity list: 1-7

RPS/RFS

前面大量介绍了多队列网卡及中断绑定,但是在单网卡单队列的情况下要想负载做网卡软中断绑定怎么办呢?RPS/RFS就是为此而生的,RPS/RFS功能出现在Linux kernel 2.6.35中,由google的工程师提交的两个补丁,这两个补丁的出现主要功能是在单队列网卡的情况下,在系统层用模拟了多队列的情况,以便达到CPU的均衡。

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

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

网卡软中断分发的软件解决方法RPS/RFS
RSS需要网卡硬件的支持,在使用不支持RSS的网卡时,为了充分利用多核cpu,centos6.1开始提供了RPS(Receive Packet Steering)和RFS(Receive Flow Steering)。
RPS使网卡可以把一个rx队列的软中断分发到多个cpu核上,从而达到负载均衡的目的。RFS是RPS的扩展,RPS只依靠hash来控制数据包,提供了好的负载平衡,但是它没有考虑应用程序的位置(注:这个位置是指程序在哪个cpu上执行)。RFS则考虑到了应用程序的位置。RFS的目标是通过指派应用线程正在运行的CPU来进行数据包处理,以此来增加数据缓存的命中率。

参考文献

多队列网卡及网卡中断绑定阐述

RECEIVE-SIDE SCALING (RSS)

多队列网卡CPU中断均衡

[精] Linux内核数据包处理流程-数据包接收(2)

Redis 高负载下的中断优化