代表中斷的分配范圍,不急,接著看一下具體的分配方式: 首先是中斷在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層12341234決定出那一層之后,最后就是在每個層次中分配節點,原則是分配在負載最小的子節點,如果負載相同則分配在中斷種類最少的節點
那么問題又來了,負載是個什么概念呢? 每個節點有各自的負載,自下而上進行計算。 處于最底層的每個邏輯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 maskdump_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 ---清除數據