深入代碼詳談irqbalance【轉】


轉自:http://blog.csdn.net/whrszzc/article/details/50533866

深入代碼詳談irqbalance

之前在工作中簡單研究了一下irqbalance,主要為了解決當時網卡性能問題,現在簡單分享一點心得,希望能對大家有一絲幫助,也歡迎大家一起討論。

總結的時候做了一個ppt,感興趣的同學可以瞅瞅
http://download.csdn.net/detail/whrszzc/9413678

本例是采用的1.0.6版本的irqbalance,代碼可以在下面網址獲取:
https://github.com/Irqbalance/irqbalance

話說在前面,由於本人很討厭直接貼上代碼的不負責行為,這里雖然是深入代碼詳談,但以總結心得為主,代碼只給出個流程。

首先,借用網上的找來的一段介紹,稍微了解下irqbalance的功能:
irqbalance用於優化中斷分配,它會自動收集系統數據以分析使用模式,並依據系統負載狀況將工作狀態置於 Performance mode 或 Power-save mode。
處於Performance mode 時,irqbalance 會將中斷盡可能均勻地分發給各個 CPU core,以充分利用 CPU 多核,提升性能。
處於Power-save mode 時,irqbalance 會將中斷集中分配給第一個 CPU,以保證其它空閑 CPU 的睡眠時間,降低能耗。(暫不討論這種模式)
簡單來說,Irqbalance的主要功能是優化中斷分配,收集系統數據並分析,通過修改中斷對於cpu的親和性來盡量讓中斷合理的分配到各個cpu,以充分利用多核cpu,提升性能。

分析代碼之前,首先來了解兩個概念,numa架構和smp_affinity。
簡單畫個圖來解釋下numa:
numa
NUMA模式是一種分布式存儲器訪問方式,處理器可以同時訪問不同的存儲器地址,大幅度提高並行性。 NUMA模式下,處理器被划分成多個”節點”(node), 每個節點被分配有的本地存儲器空間。 所有節點中的處理器都可以訪問全部的系統物理存儲器,但是訪問本節點內的存儲器所需要的時間,比訪問某些遠程節點內的存儲器所花的時間要少得多。
irqbalance就是根據這種架構來分配中斷的。主要的原因是避免終端在節點中遷移產生過多的代價。

smp_affinity是用來設置中斷親緣的CPU的mask碼,簡單來說就是在cpu上分配中斷。
SMP affinity is controlled by manipulating files in the /proc/irq/ directory.
In /proc/irq/ are directories that correspond to the IRQs present on your
system (not all IRQs may be available). In each of these directories is
the “smp_affinity” file, and this is where we will work our magic.

好的,廢話不多說,下面在代碼層面介紹irqbalance,具體的流程會在最后貼出。

首先看一下irqbalance用到的數據結構是什么樣的:
這里寫圖片描述
簡單說就是根據cpu的結構由上到下建立了一個樹形結構,當然,為了平衡終端,每個節點還會掛接本節點分配的中斷。

樹形結構建立好之后,自然是開始分配中斷。irqbalance中把中斷分成了八種類型:

#define IRQ_OTHER 0 #define IRQ_LEGACY 1 #define IRQ_SCSI 2 #define IRQ_TIMER 3 #define IRQ_ETH 4 #define IRQ_GBETH 5 #define IRQ_10GBETH 6 #define IRQ_VIRT_EVENT 7

依據就是pci設備初始化時注冊的類型:/sys/bus/pci/devices/0000:00:01.0/class
每種中斷類型又分別對應一種分配方式,分配方式一共有四種:

BALANCE_PACKAGE
BALANCE_CACHE
BALANCE_NONE
BALANCE_CORE

代表中斷的分配范圍,不急,接着看一下具體的分配方式:
首先是中斷在numa_node中分配,有兩種情況:
/sys/bus/pci/devices/0000:00:01.0/numa_node中指定了非-1的numa_node,則把中斷分配到對應的numa;如果是-1的話,則根據中斷數平均的分到兩個numa
分配好numa_node之后開始在整個樹中進行分配,分配哪一個層次的原則是

BALANCE_NONE分配在numa_node層
BALANCE_PACKAGE分配在package層 BALANCE_CACHE分配在cache層 BALANCE_CORE分配在core層

決定出那一層之后,最后就是在每個層次中分配節點,原則是分配在負載最小的子節點,如果負載相同則分配在中斷種類最少的節點

那么問題又來了,負載是個什么概念呢?
每個節點有各自的負載,自下而上進行計算。
處於最底層的每個邏輯cpu的負載的計算方法是:
在/proc/stat獲取每個cpu的信息如下
cpu0 2383 0 298701 468097 158010 572 121175 0 0 0
取第6、7項,分別代表從系統啟動開始累計到當前時刻,硬中斷、軟中斷時間(單位是jiffies),然后將累加的值轉換成納秒單位,轉換方法是:和*1*10^9/HZ。
了解了邏輯cpu的負載的計算方法不難得到負載所表示的意義:單位時間(10s)內,cpu處理軟中斷加上硬中斷的時間的和

邏輯cpu這一層的負載計算完成之后,要開始計算上層節點的負載情況,計算方法是父節點負載等於各孩子節點負載的和的平均值,自下向上進行運算,如下圖所示
這里寫圖片描述
應該很容易理解吧

到此為止,我們已經得到了各個節點的負載情況,那么下一步是做什么呢?irqbalance的最終目的在於平衡中斷,現在環境已經搭建好了,就差平衡中斷了。但是,平衡之前還有一件事情要做,就是計算每個中斷的負載。中斷的負載不同於前面說的負載,運算比較復雜,等於本層次單位中斷的負載情況再乘以每個中斷新增個數,中斷的負載也是自下向上進行運算,
有點暈?
詳細解釋一下:中斷最終是運行在某一個cpu上的,所以有的中斷雖然分配在cache、package層次上,但是最終還是在cpu上運行,所有每個cpu執行中斷數大概等於所有父節點的中斷數一級一級平均下來。然后用該cpu的負載除以該cpu平均處理的中斷數,得到單位中斷所占用的負載,那么每個中斷的負載就等於該中斷在單位時間內新增的個數乘以單位中斷所占用的負載
計算方法稍微說明一下:
首先是各節點的平均中斷數的計算,每個節點的中斷數等於父節點的中斷數除以該節點的個數再加上該節點的中斷數,注意:這里說的中斷數不是中斷的種數,是所有中斷的新增的個數的和
然后用每個節點的負載除以該節點的平均處理的中斷數,得到該節點單位中斷所占用的負載
最后針對每一個中斷,用該中斷在單位時間內(10s)新增的個數乘以單位中斷所占用的負載,得到每個中斷自己的負載情況。附上圖示:
這里寫圖片描述

前方高能!
最后,也就是到了最終的臨門一腳,開始分配中斷。平衡算法如下:
得到每個節點的負載以及每個中斷的負載之后,就需要找到負載較高的節點,把該節點的中斷從節點中移動到其他的節點來平衡每個cpu的中斷。簡單來說,是統計每一個層次所有節點的負載的離散狀態,找出偏差比較高的節點,把一個或多個中斷從本節點剔除,重新分配到該層次負載較小的節點,來達到平衡的目的

取cpu層次的來解釋一下,其他層次類似:
經過前面的計算已經得到了每個cpu的負載,也就是得到了一些樣本數據,接下來計算負載的平均值和標准差(用於描述數據的離散情況)
接下來是找出負載異常的樣本數據,方法找到負載數據與平均值的差大於標准差的樣本,有一個前提是該樣本所包含的中斷種數需要多於1種,然后把該樣本中的中斷按照中斷的負載情況由大到小進行排序,依次從該節點移除,直到該節點的負載情況小於等於平均值為止
最后就是把剔除的中斷重新進行分配,分配的時候是選取負載最小的節點進行分配

平衡算法我個人認為是irqbalance中最核心的一個部分,也是最容易出問題的部分。為什么呢?放在最后再說。。

先整理一下irqbalance的流程:
初始化的過程只是建立鏈表的過程,暫不描述,只考慮正常運行狀態時的流程
-處理間隔是10s
-清除所有中斷的負載值
-/proc/interrupts讀取中斷,並記錄中斷數
-/proc/stat讀取每個cpu的負載,並依次計算每個層次每個節點的負載以及每個中斷的負載
-通過平衡算法找出需要重新分配的中斷
-把需要重新分配的中斷加入到新的節點中
-配置smp_affinity使處理生效

至於最后的smp_affinity是如何設置的在此不再贅述,不懂的可以稍微了解一下,比較簡單。
irqbalance支持用戶配置每個中斷的分配情況,設置在/proc/irq/#irq/affinity_hint中,irqbalance有三種模式處理這個配置
EXACT模式下用戶設置的cpu掩碼強制生效
SUBSET模式下,會盡量把中斷分配到用戶指定的cpu上,最終生效的是用戶設置的掩碼和中斷所屬節點的掩碼的交集
IGNORE模式下,不考慮用戶的配置

最后總結一下irqbalance:
irqbalance比較適合中斷種類非常多,單一中斷數量並不是很多的情況,可以很均衡的分配中斷
如果遇到中斷種類過少或者是某一個中斷數量過大,會導致中斷不停的在cpu之間遷移,每10s遷移一次,會降低系統性能,並且會導致過多的中斷偶爾同時集中同一個cpu上(原因有二,一是平衡中斷時優先轉移的是負載較大的中斷;二是沒有計算平衡之后的負載情況)
irqbalance的計算是建立在假設每種中斷的處理時間大概相等的情況下,實際的真實狀態可能並非如此
irqbalance對於中斷的遷移只能在規定的作用域之內進行遷移,特別的,對於numa來說,一旦大部分中斷被分配到了同一個numa上,則不論如何平衡,都不會使中斷遷移到另一個numa的cpu上

最后的最后,既然說要深入代碼詳談,就稍微貼一段代碼的流程吧

流程: build_object_tree ---建立cpu/cache/package的二叉樹,並打印。讀取pci硬件注冊的中斷,並建立數據鏈 force_rebalance_irq ---把所有irq加到rebalance_irq_list鏈表中 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷不處理 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,每層次負載等於子節點負載總和除以子結點個數 ---輸出“-----------------------------------------sleep_approx(SLEEP_INTERVAL) ---等待10秒 clear_work_stats ---清除中斷的負載值,不同於之前記錄的中斷個數 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷不處理 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,每層次負載等於子節點負載總和除以子結點個數 calculate_placement ---先把rebalance_irq_list中的中斷移動到numa節點,然后從numa節點開始由上而下分發中斷,依據是中斷的level和子節點的負載情況,優先選擇負載小的子節點,如果相同則選擇中斷個數少的子節點 activate_mappings ---配置smp_affinity,exact模式下,直接使用affinity_hint下發,SUBSET模式下,使用affinity_hint和中斷所屬節點的cpu mask的交集,其他模式使用irq所屬節點的cpu mask dump_tree ---把中斷分布情況打印出來,cycle_count++ while (keep_going) ---第一個循環 sleep_approx(SLEEP_INTERVAL) ---等待10秒 clear_work_stats ---清除中斷的負載值,不同於之前記錄的中斷個數 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷加入到new_irq_list中並置need_rescan標記 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,和各層次中斷的負載。方法在上面 if (need_rescan) ---由於有新增中斷,需要重新建立表,need_rescan置0 ---輸出“Rescanning cpu topology” reset_counts ---清零中斷的計數 clear_work_stats ---清零中斷的負載 free_object_tree ---清除所有的二叉樹和中斷數據鏈 build_object_tree ---重新建立cpu/cache/package的二叉樹,並打印。讀取pci硬件注冊的中斷,並建立數據鏈,此處會把新增中斷new_irq_list加入到中斷鏈中 force_rebalance_irq ---把所有irq加到rebalance_irq_list鏈表中 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷加入到new_irq_list中並置need_rescan標記 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,每層次負載等於子節點負載總和除以子結點個數 sleep_approx(SLEEP_INTERVAL) ---等待10秒---主要為了統計計數 clear_work_stats ---清零中斷的負載 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷加入到new_irq_list中並置need_rescan標記 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,每層次負載等於子節點負載總和除以子結點個數 ---cycle_count置0 calculate_placement ---先把rebalance_irq_list中的中斷移動到numa節點,然后從numa節點開始由上而下分發中斷 activate_mappings ---配置smp_affinity dump_tree ---把中斷分布情況打印出來,cycle_count++ while (keep_going) ---正常循環 sleep_approx(SLEEP_INTERVAL) ---等待10秒 clear_work_stats ---清除中斷的負載值,不同於之前記錄的中斷個數 parse_proc_interrupts ---在/proc/interrupts讀取中斷,並記錄中斷個數,新中斷加入到new_irq_list中並置need_rescan標記 parse_proc_stat ---在/proc/stat讀取每個cpu的中斷負載,由下向上計算各層次負載,和各層次中斷的負載。方法在上面 update_migration_status ---計算各節點的標准差和平均值,把負載大於平均值的節點中的中斷,按照負載從小到大的形式加入到rebalance_irq_list,直到負載小於平均值或者中斷數為1 calculate_placement ---先把rebalance_irq_list中的中斷移動到numa節點,然后從numa節點開始由上而下分發中斷 activate_mappings ---配置smp_affinity dump_tree ---把中斷分布情況打印出來,cycle_count++ free_object_tree ---清除數據


免責聲明!

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



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