多隊列網卡CPU中斷均衡


一、基礎

 1.相關名詞

IRQ

Interrupt Request,中斷請求,從硬件層發出 
作用:執行硬件中斷的請求

SMP(Symmetrical Multi-Processing)

對稱多處理器系統,是指在一個計算機上匯集了一組CPU,各CPU之間共享內存子系統以及總線結構(或者說是兩個或多個同樣的處理器通過一塊共享內存彼此連接。) 
作用:適用於多處理器計算機

APIC(Advanced Programmable Interrupt Controllers)

高級可編程中斷控制器

松耦合多處理架構

最早的Linux SMP是松耦合多處理系統。這些系統是利用多個高速互連的單一系統構造的(如 10G 以太網、Fibre Channel 或 Infiniband)。構建松耦合多處理系統很容易,但是構建大型的多處理器網絡可能占用相當大的空間並消耗很多電量。因為它們通常是利用普通硬件來構建的,所以包含的有些硬件不相關卻要耗費很多電量和空間。更大的缺點在於通信結構。即使使用高速網絡(如 10G 以太網),也存在系統可伸縮性的限制。

CMP

芯片多級處理。CMP一種緊密耦合多處理器,可以將它看作將松耦合架構縮小至芯片級。即在一個集成電路中,多個芯片、共享內存以及互連形成了一個緊密集成的多處理核心

2.中斷的相關概念

  Linux 內核對計算機上所有的設備進行管理,進行管理的方式是內核和設備之間的通信。解決通信的方式有兩種: 
1. 輪詢。輪詢是指內核對設備狀態進行周期性的查詢 
2. 中斷。中斷是指在設備需要CPU的時候主動發起通信

從物理學的角度看,中斷是一種電信號,由硬件設備產生,並直接送入中斷控制器(如 8259A)的輸入引腳上,然后再由中斷控制器向處理器發送相應的信號。處理器一經檢測到該信號,便中斷自己當前正在處理的工作,轉而去處理中斷。此后,處理器會通知 OS 已經產生中斷。這樣,OS 就可以對這個中斷進行適當的處理。不同的設備對應的中斷不同,而每個中斷都通過一個唯一的數字標識,這些值通常被稱為中斷線。

  • 中斷可以分為NMI(不可屏蔽中斷)和INTR(可屏蔽中斷)

其中 NMI 是不可屏蔽中斷,它通常用於電源掉電和物理存儲器奇偶校驗;INTR是可屏蔽中斷,可以通過設置中斷屏蔽位來進行中斷屏蔽,它主要用於接受外部硬件的中斷信號,這些信號由中斷控制器傳遞給 CPU。

  • 常見的兩種中斷控制器:

1.可編程中斷控制器(PIC)8259A 
2.高級可編程中斷控制器(APIC)

傳統的 PIC(Programmable Interrupt Controller)是由兩片 8259A 風格的外部芯片以“級聯”的方式連接在一起。每個芯片可處理多達 8 個不同的 IRQ。因為從 PIC 的 INT 輸出線連接到主 PIC 的 IRQ2 引腳,所以可用 IRQ 線的個數達到 15 個

二、多隊列網卡CPU中斷均衡

注意:本文全部是基於多核CPU環境而寫,如果是單核CPU,沒有任何意義

首先。我們要先判斷當前系統環境是否支持多隊列網卡,執行命令:

lspci -vvv

Paste_Image.png

注意上圖中紅色部分。如果在Ethernet項中。含有[a0] MSI-X: Enable+ Count=9 Masked-語句,則說明當前系統環境是支持多隊列網卡的,否則不支持。對於不支持多網卡隊列的CPU均衡,將在下文分析。

1. Linux系統中斷設置原理

為了在支持SMP的硬件上通過Linux使用SMP,需要適當的配置內核。 
以網卡中斷為例,如果流量大了,一個CPU處理會十分吃力,甚至崩潰;因為CPU處於忙碌狀態,此時系統性能低,不能較快處理網卡接收的數據包。數據包堆積,網卡緩存溢出,導致丟包。這也是為什么負載高時會有網絡丟包的原因了。但是服務器明明是多核CPU,為什么其他CPU沒有參與處理了?原因是CPU沒有做均衡時,默認是在CPU0上執行中斷。雖然系統服務本身是有/etc/init.d/irqbalance 這個服務的作用就是用來做CPU均衡的,但是對於處理流量很大的服務器來說,這個服務的效果就微乎其微了,CPU均衡沒有達到最優。 
注意:如果要使用SMP(對稱多處理系統),CPU需要內置APIC

中斷綁定(CPU均衡)分為單隊列網卡和多隊列網卡兩種情況。對於多隊列網卡,開啟SMP,如果只是開啟SMP可能不會使CPU中斷均衡達到最優。 
可以同時開啟SMP和RPS/RFS,使得CPU中斷均衡達到最優(因為CPU核心可能會更多,但是網卡隊列只有4-8個之類的,這個需要看具體機機型)。對於單隊列網,只能開啟RPS/RFS。

中斷綁定——中斷親和力(IRQ Affinity) 
維持親和性是為了提高緩存效率

在 SMP 體系結構中,我們可以通過調用系統調用和一組相關的宏來設置 CPU 親和力(CPU affinity),將一個或多個進程綁定到一個或多個處理器上運行。中斷在這方面也毫不示弱,也具有相同的特性——中斷親和力。中斷親和力是指將一個或多個中斷源綁定到特定的 CPU 核心上運行。 
在 /proc/irq 目錄中,對於已經注冊中斷處理程序的硬件設備,都會在該目錄下存在一個以該中斷號命名的目錄,該目錄下有一個 smp_affinity 文件(SMP 體系結構才有該文件),文件中的數據表示 CPU 位掩碼,可以用來設置該IRQ與某個CPU的親和力(默認值為 0xffffffff,表明把中斷發送到所有的 CPU 上去處理),通過指定CPU 核心與某個中斷的親和性后,中斷所對應的硬件設備發出的中斷請求就都會給這個CPU核心處理。 
如果中斷控制器不支持 IRQ affinity,不能改變此默認值,同時也不能關閉所有的 CPU 位掩碼,即不能設置成 0x0。 
注意:SMP 綁定irq 到網卡 只對多隊列網卡生效。其實也可以通過查看/proc/interrupt來查看系統是否支持多隊列網卡——即,如果interrupt文件中含有ethN-xxx的就是多隊列,如果只是ethN的就是單隊列 
**

2.相關目錄文件以及實例

2.1中斷相關文件

  • /proc/interrupts:該文件存放了每個I/O設備的對應中斷號、每個CPU的中斷數、中斷類型。
  • /proc/irq/:該目錄下存放的是以IRQ號命名的目錄,如/proc/irq/40/,表示中斷號為40的相關信息
  • /proc/irq/[irq_num]/smp_affinity:該文件存放的是CPU位掩碼(十六進制)。修改該文件中的值可以改變CPU和某中斷的親和性
  • /proc/irq/[irq_num]/smp_affinity_list:該文件存放的是CPU列表(十進制)。注意,CPU核心個數用表示編號從0開始,如cpu0,cpu1等
  • smp_affinity_list和smp_affinity任意更改一個文件都會生效,兩個文件相互影響,只不過是表示方法不一致,但一般都是修改smp_affinity 文件

  • 以8核CUP為例,列出相關文件中如何表示CPU列表

 cup     二進制     smp_affinity_list      smp_affinity(十六進制)
cpu0     0001 0 1 cpu1 0010 1 2 cpu2 0100 2 4 cpu3 1000 3 8 cpu4 010000 4 10 cpu5 0100000 5 40 ......如上類推
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在計算cpu親和性時很容易混淆,我們先排除smp_affinity_list這項不看。只看二進制和和十六進制這兩項。這里可以得出一個公式: 
Python語法:

smp_affinity_value = hex(2**(N-1))

其中N代表的是CPU核心數。那么,為什么是”N-1”呢。原因很簡單,因為對於多核服務器而言,cpu編號是從cpu0開始的。比如24核心的服務器,cpu編號為cpu0-cpu23。

2.2 中斷親和性設置實例——以以太網卡的中斷為例

  • 動態監控CPU中斷情況,觀察中斷變化

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

  • 查看網卡中斷相關信息

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

  • 網卡親和性設置

修改proc/irq/irq_number/smp_affinity之前,先停掉irq自動調節服務,不然修改的值就會被覆蓋。

/etc/init.d/irqbalance stop

通過查看網卡中斷相關信息,得到網卡中斷為19

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

修改值,將19號中斷綁定在cpu2上:

[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
  • 1
  • 2
  • 3
  • 4
  • 5

如果是要將網卡中斷綁定在cpu0和cpu2上怎么做了?請先參照上文中的CPU列表。cpu0和2的十六進制值分別為1,4。那么如果要同時綁定在cpu0和cpu2上,則十六進制值為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
  • 1
  • 2
  • 3
  • 4
  • 5

再做一個連續綁定的例子,如綁定在cpu0,1,2上:

[root@master 19]# cat smp_affinity 00000000,00000000,00000000,00000007 [root@master 19]# cat smp_affinity_list 0-2
  • 1
  • 2
  • 3
  • 4

從這里可以得出一個結論:綁定單個cpu只要寫數字就行,如果是綁定多個cpu則用逗號隔開,如果是綁定連續CPU,則用-符號。

注意:寫入smp_affinity中的必須是16進制(不帶0x標識),更新這個文件后,smp_affinity_list也會更新,這個文件里面是10進制。

我們在計算CPU 核心編號時,是以二進制算的,但是文件中要存放的是十六進制(不帶0x標識)。如CPU 0表示的是第一個核心,二進制為0001,十六進制為1。該算法可參考前述的CPU 列表對應關系。 
再比如,f 十六進制是15,二進制就是1111,這個就是表示設備隨機選擇一個CPU執行中斷。

三、使用taskset為系統進程PID設置CPU親和性

  • 查看某個進程的CPU親和性
# taskset -p 30011 pid 30011's current affinity mask: ff
  • 1
  • 2
  • 設置某個進程的CPU親和性
 # taskset -p 1 30011 pid 30011's current affinity mask: ff pid 30011's new affinity mask: 1
  • 1
  • 2
  • 3
  • 使用-c選項可以將一個進程對應到多個CPU上去
# 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

四、多隊列網卡中斷綁定——CPU中斷均衡腳本

eth_irq.py

#!/usr/bin/python import re from multiprocessing import cpu_count dir = '/proc/irq' interrupt = '/proc/interrupts' class IRQ(): def __init__(self): self.irq_num = [] with open(interrupt, 'r') as f: for i in f: if re.search(r'eth|em', i): #print re.split(r'\s*|:', i)[1] self.irq_num.append(re.split(r'\s*|:', i)[1].split(':')[0]) #if re.search('eth', i): # print re.split(r'\s*|:', i) # self.irq_num.append(re.split(r'\s|:', i)[2]) print self.irq_num self.cpu_num = cpu_count() self.mask = [hex(2**i).split('0x')[1] for i in range(self.cpu_num)] self.set_affinity() def set_affinity(self): affinity_file = [] for i in self.irq_num: affinity_file.append('%s/%s/smp_affinity' % (dir, i)) #print affinity_file #print self.mask self.mask.extend(self.mask) #print self.mask for i in range(len(self.mask)): print '%s %s' % (self.mask[i], affinity_file[i]) with open(affinity_file[i], 'w') as f: f.write("%s" % self.mask[i]) if __name__ == '__main__': a = IRQ() print a.mask
  • 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

這個腳本寫的有點死,不夠靈活,有時間再重寫一下,做一個封裝。

執行上面腳本運行前,可執行test.sh查看smp_affinity_list中的值的變化

test.sh

#!/bin/bash irq=`grep 'eth' /proc/interrupts | awk '{print $1}' | cut -d : -f 1` for i in $irq do num=`cat /proc/irq/$i/smp_affinity_list` echo /proc/irq/$i/smp_affinity_list" "$num done
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

前(其實之前已經做過均衡,我這里只是改了下)

/proc/irq/59/smp_affinity_list 2 /proc/irq/60/smp_affinity_list 3 /proc/irq/61/smp_affinity_list 4 /proc/irq/62/smp_affinity_list 5 /proc/irq/63/smp_affinity_list 6 /proc/irq/64/smp_affinity_list 7 /proc/irq/65/smp_affinity_list 0 /proc/irq/66/smp_affinity_list 1 /proc/irq/68/smp_affinity_list 2 /proc/irq/69/smp_affinity_list 3 /proc/irq/70/smp_affinity_list 4 /proc/irq/71/smp_affinity_list 5 /proc/irq/72/smp_affinity_list 6 /proc/irq/73/smp_affinity_list 7 /proc/irq/74/smp_affinity_list 0 /proc/irq/75/smp_affinity_list 1 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

/proc/irq/59/smp_affinity_list 0 /proc/irq/60/smp_affinity_list 1 /proc/irq/61/smp_affinity_list 2 /proc/irq/62/smp_affinity_list 3 /proc/irq/63/smp_affinity_list 4 /proc/irq/64/smp_affinity_list 5 /proc/irq/65/smp_affinity_list 6 /proc/irq/66/smp_affinity_list 7 /proc/irq/68/smp_affinity_list 0 /proc/irq/69/smp_affinity_list 1 /proc/irq/70/smp_affinity_list 2 /proc/irq/71/smp_affinity_list 3 /proc/irq/72/smp_affinity_list 4 /proc/irq/73/smp_affinity_list 5 /proc/irq/74/smp_affinity_list 6 /proc/irq/75/smp_affinity_list 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

五、單隊列多網卡CPU中斷均衡

使用RPS/RFS在軟件層面模擬多隊列網卡功能。RPS/RFS是谷歌工程師提交的內核補丁。意在處理多核CPU單隊列網卡的情況。

RFS需要內核編譯CONFIG_RPS選項,RFS才起作用。全局數據流表(rps_sock_flow_table)的總數可以通過下面的參數來設置: 
/proc/sys/net/core/rps_sock_flow_entries

每個隊列的數據流表總數可以通過下面的參數來設置:

/sys/class/net/[iface]/queues/rx-/rps_cpus /sys/class/net/[iface]/queues/rx-/rps_flow_cnt /proc/sys/net/core/rps_sock_flow_entries
  • 1
  • 2
  • 3
  • /sys/class/net/[iface]/queues/rx-/rps_cpus 
      該文件存放的是對應的CPU核心,如果值為f…則表示每個隊列綁定到所有cpu核心上;如果值為1,2之類的,則表示為綁定對應的CPU核心。如:對於物理CPU個數為2,邏輯CPU為8核心的機器,具體計算方法是第一顆cpu是00000001,第二個cpu是00000010,第3個cpu是 00000100,依次類推,由於是所有的cpu都負擔,所以所有的cpu數值相加,得到的數值為11111111,十六進制就剛好是ff。ff就表示綁定到所有CPU核心上。
  • /proc/sys/net/core/rps_sock_flow_entries 
      該數值是根據網卡有多少個個通道計算得出的數據,例如8通道的網卡,那么1個網卡,每個通道設置4096的數值,8*4096就是/proc/sys/net/core/rps_sock_flow_entries 的數值,對於內存大的機器可以適當調大rps_flow_cnt

每個隊列分別綁定到一個對應的CPU核心上

/sys/class/net/eth1/queues/rx-0/rps_cpus 1(這里的數據是十六進制,文件中為:000001) /sys/class/net/eth1/queues/rx-1/rps_cpus 2 /sys/class/net/eth1/queues/rx-2/rps_cpus 4 /sys/class/net/eth1/queues/rx-3/rps_cpus 8 /sys/class/net/eth1/queues/rx-4/rps_cpus 10 /sys/class/net/eth1/queues/rx-5/rps_cpus 20 /sys/class/net/eth1/queues/rx-6/rps_cpus 40 /sys/class/net/eth1/queues/rx-7/rps_cpus 80 /sys/class/net/eth1/queues/rx-0/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-1/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-2/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-3/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-4/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-5/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-6/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-7/rps_flow_cnt 4096 /proc/sys/net/core/rps_sock_flow_entries 32768
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

每個隊列綁定到所有CPU核心上

/sys/class/net/eth1/queues/rx-0/rps_cpus ff /sys/class/net/eth1/queues/rx-1/rps_cpus ff /sys/class/net/eth1/queues/rx-2/rps_cpus ff /sys/class/net/eth1/queues/rx-3/rps_cpus ff /sys/class/net/eth1/queues/rx-4/rps_cpus ff /sys/class/net/eth1/queues/rx-5/rps_cpus ff /sys/class/net/eth1/queues/rx-6/rps_cpus ff /sys/class/net/eth1/queues/rx-7/rps_cpus ff /sys/class/net/eth1/queues/rx-0/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-1/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-2/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-3/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-4/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-5/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-6/rps_flow_cnt 4096 /sys/class/net/eth1/queues/rx-7/rps_flow_cnt 4096 /proc/sys/net/core/rps_sock_flow_entries 32768
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如果不開啟rps功能,則rps_cpus 文件中的值設置為0

七、IRQ均衡腳本

寫腳本時發現很多問題,如下面的兩台主機,都是支持多隊列網卡的,但是注意觀察最后兩列的區別

[root@master ~]# grep 'eth' /proc/interrupts 59: 61033490 0 3671863 0 0 0 0 0 PCI-MSI-edge eth0-0 60: 41459456 3308875 0 2914342 0 0 0 0 PCI-MSI-edge eth0-1 61: 54622748 0 3360309 0 4103929 0 0 0 PCI-MSI-edge eth0-2 62: 310768180 0 0 32393316 0 31050595 0 0 PCI-MSI-edge eth0-3 63: 47763053 0 0 0 3674309 0 3352638 0 PCI-MSI-edge eth0-4 64: 66969322 0 0 0 0 5011122 0 5180864 PCI-MSI-edge eth0-5 65: 50396675 0 0 0 0 0 3175900 0 PCI-MSI-edge eth0-6 66: 44104243 3138376 0 0 0 0 0 3091676 PCI-MSI-edge eth0-7 68: 3994017501 0 338732695 0 0 0 0 0 PCI-MSI-edge eth1-0 69: 2203747223 766400094 0 818107598 0 0 0 0 PCI-MSI-edge eth1-1 70: 3089604544 0 759400051 0 795843526 0 0 0 PCI-MSI-edge eth1-2 71: 1894677558 0 0 760811049 0 793309576 0 0 PCI-MSI-edge eth1-3 72: 805024305 0 0 0 723044628 0 759874105 0 PCI-MSI-edge eth1-4 73: 1582319475 0 0 0 0 721360118 0 781055635 PCI-MSI-edge eth1-5 74: 2854078786 0 0 0 0 0 709514558 0 PCI-MSI-edge eth1-6 75: 1699542056 779165245 0 0 0 0 0 717908550 PCI-MSI-edge eth1-7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
[root@minion ~]# grep 'eth' /proc/interrupts 90: 6 0 0 0 0 0 0 0 PCI-MSI-X eth0 98: 3178016492 23261814 26903212 24899595 2274387599 284769513 2180244656 385019532 PCI-MSI-X eth0-rx-0 106: 1442 411400813 25969662 6659594 2117294006 142635319 2079125696 331603756 PCI-MSI-X eth0-rx-1 114: 6626 20485044 2713744686 6831426 3331230251 4042587617 992902361 1019496640 PCI-MSI-X eth0-rx-2 122: 771 38602882 23569683 1670873952 1400480565 1479682722 878554708 1266571848 PCI-MSI-X eth0-rx-3 130: 676 4001308 121711 241193970 4054343807 10 2219979772 2992231001 PCI-MSI-X eth0-tx-0 146: 8 0 0 0 0 0 0 0 PCI-MSI-X eth1 154: 1208 304770485 506778587 3164891700 6749496 3992464600 7582534 610809 PCI-MSI-X eth1-rx-0 162: 24012 923562439 1868789464 430527766 5200172 4789746 978408581 297773 PCI-MSI-X eth1-rx-1 170: 179444 589446443 591027856 173978259 2152491 2082150 2069743 1894849209 PCI-MSI-X eth1-rx-2 178: 2445302424 1728604344 843543689 232630412 4365207 4219576 4031133 502923 PCI-MSI-X eth1-rx-3 186: 16761 325082928 712828704 249179575 2839276 2725


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM