本文檔基於irqbalance-1.5.0
源碼鏈接:https://launchpad.net/ubuntu/+source/irqbalance/
1. object tree
Irqbalance是用戶空間用於優化中斷的一個工具,通過周期性的(默認10s)統計各個cpu上的中斷情況,重新對中斷進行再分配,實現各個cpu上中斷負載相對均衡。中斷均衡是建立再“object tree”的基礎之上的,object tree則是通過系統的拓撲結構建立的分層結構。根據系統結構屬性NUMA node/packet/cache affinity可以將系統划分為自上而下的四層:node->package->cache->cpu。
以16核雙路服務器為例,系統有兩個numa node,每個節點包含兩個cluster,每個cluster包含4個cores,共享l2 cache。其結構簡圖如下:
對應的object tree拓撲結構如圖:
其中:
(1)每個節點為一個object,通過struct topo_obj描述。
(2)上下層之間的節點通過parent/child指針管理
(3)每一層都有一個全局鏈表頭指針,用於組織管理處於同一層的所有節點。
2. 數據結構
2.1 Irq_info
在樹形拓撲結構建立之后,就需要統計各個節點上中斷負載的信息,以便為重新分配中斷提供依據。對於各個中斷信息通過struct irq_info來描述。下面結合各個字段介紹下irqlabalance中幾個比較關鍵的概念。
struct irq_info { int irq; //中斷號 int class; int type; int level; int flags; struct topo_obj *numa_node; //中斷當前所在node節點對應的object cpumask_t cpumask; uint64_t irq_count; uint64_t last_irq_count; uint64_t load; int moved; struct topo_obj *assigned_obj; //中斷被分配到節點對應的object char *name; };
(1) 中斷類型(irq_info.class)
Irqbalance根據中斷所屬device的pci配置空間class code把中斷分成了以下8種類型字。
#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
其中:irq_info.class 與 pci配置空間 class code的映射關系如下:
(2) 中斷層級(irq_info.level)
每種中斷類型根據靜態映射分別對應一種分配方式,分配方式一共有4種。
#define BALANCE_NONE 0 //表示中斷不能進行遷移 #define BALANCE_PACKAGE 1 //表示中斷只能在package層進行均衡 #define BALANCE_CACHE 2 //表示中斷只能在cache層進行均衡 #define BALANCE_CORE 3 //表示中斷只能在core層進行均衡
中斷類型與其映射關系如表:
(3) 中斷計數
irq_info.irq_count 表示本次統計irq中斷在各個cpu上產生的中斷次數之和。
irq_info.last_irq_count表示上次統計irq中斷在各個cpu上的產生的中斷次數之和。
(4) 中斷的負載
表示兩次統計這段時間內中斷增加所帶來的負擔。在irqbalance中把中斷消耗的時間來衡量cpu的負擔。
計算方法詳見3.4(4).
2.2. topo_obj
“對象樹“中的每個節點均為一個對象,通過struct topo_obj進行描述。
struct topo_obj { uint64_t load; uint64_t last_load; uint64_t irq_count; enum obj_type_e obj_type; //區別該節點位於哪一層。 int number; // object對應的索引 int powersave_mode; cpumask_t mask; //該節點下包含哪幾個cpu GList *interrupts; //組織被分配到該obj的中斷 struct topo_obj *parent; //指向其上一層父對象的obj GList *children; //組織管理該obj包含的下層obj GList *numa_nodes; //指向最頂層所在node節點的obj GList **obj_type_list; };
(1) object節點的負載(topo_obj.load)
- cpu節點的load計算方法:
在/proc/stat獲取每個cpu的信息如下:
其中第6/7項,分別代表自系統啟動以來硬/軟中斷累計時間(單位是jiffies)。
cpu負載:單個周期(10s)內,cpu處理軟、硬中斷的時間之和。
- cpu拓撲層以上層各個節點的負載:
父節點負載等於各孩子節點負載的總和 。
(2) 節點中斷管理指針(topo_obj.interrupts)
所有被分配到該節點上的中斷都掛在該指針上。
(3) 節點中斷計數(topo_obj.irq_count)
分配到該節點上的所有中斷(即interrupts指針管理的鏈表上的中斷)在單位周期(10s)內增加的次數之和。
(4) 節點的object類型(topo_obj.obj_type)
object的類型,用來表明該對象處在對象樹的哪一層次。Irqbalance 定義了如下4種類型。
OBJ_TYPE_CPU,
OBJ_TYPE_CACHE,
OBJ_TYPE_PACKAGE,
OBJ_TYPE_NODE
(5) powersave_mode
用來表示該object是否處在省電模式。具體介紹詳見4。
Irqbalance默認是關閉powersave_mode的,但是用戶可以通過irqbalance -p <n>來設置power_thresh。當系統內object的負載較小時,會自動切換到省電模式。
3. irqbalance處理流程
Irqbalance會周期性的(10s)統計系統中斷的情況,主要的處理流程圖如下:
下面針對各個部分作具體介紹:
3.1 build_object_tree
(1)作用:
主要實現建立拓撲結構各個節點以及中斷數據結構,並初始化。
(2)實現:
通過遍歷/sys/devices/system/node/node[*],決定有多少OBJ_TYPE_NODE的對象。
通過遍歷/sys/devices/system/cpu/cpu[*],以及是否online,決定有多少OBJ_TYPE_CPU的對象
通過遍歷/sys/devices/system/cpu/cpu[*]/cache/index[MAX]/shared_cpu_map決定有多少OBJ_TYPE_CACHE對象。
通過遍歷/topology/physical_package_id決定有多少OBJ_TYPE_PACKAGE的對象。
通過遍歷/sys/bus/pci/devices/0000:00:[**].[*]/下irq以及msi,建立各個irq的數據,並結合proc/irq/下文件初始化中斷數據結構。這樣irqbalance就知道該irq屬於哪個node以及smp_affinity.
3.2 clear_work_stats
清除各個對象節點上分配中的負載值,以便重新賦值。
3.3 parse_proc_interrupts
通過分析/proc/interrupts文件,更新各個irq在所有cpu上中斷次數的累加和。
3.4 parse_proc_stats
(1) 計算各個對象節點的load,並更新到topo_obj.load。
cpu節點的負載: 通過分析/proc/stats統計硬/軟中斷的累計時間。
其余節點的負載:父節點負載=各孩子節點負載的總和
(2) 計算各個對象節點上所有中斷在單位周期(10s) 內新增加的次數,並更新到load_slice.irq_count。
(3) 計算各個對象節點上單位周期內平均中斷數local_count。
由於分配到上層節點的中斷最終要“均分”給下層,所以obj平均中斷數loacl_count=obj.irq_cpunt +parent_obj.irq_count/(parent_obj的子節點數)。如圖所示:
(4) 計算對象上各個中斷負載 irq_info.load
單次中斷對obj節點的負載貢獻值(load_slice),即節點負載除以平均中斷數:
load_slice = topo_obj.load/local_count.
那么該對象節點上各個中斷負載就等於單次中斷負載貢獻值與中斷次數的乘積:
irq_info.load= load_slice*(irq_info.irq_count- irq_info.last_irq_count)
3.5 update_migration_status
通過平衡算法找出需要重新分配的中斷。自下而上遍歷各個拓撲層,針對每一層:
(1)遍歷該層各個對象節點,計算該層的平均負載avg_load、標准方差std_deviation以及節點中負載最小值min_load.
(2)再次遍歷該層各個拓撲節點,找到大於min_load的節點,然后把該節點中的中斷按照中斷的irq_info.class由大到小並負載情況由大到小進行排序,然后依次從該節點移除,放到表頭為rebalance_irq_list的鏈表中。
注意:中斷從節點遷移后會更新該節點的負載以及min_load,當兩者最接近時停止遷移中斷。
也就是說該步驟過后,需要遷移的中斷都被放在了表頭為rebalance_irq_list的鏈表中,后續會將這些中斷重新分配。
3.6 calculate_placement
(1) 將存在rebalance_irq_list鏈表中的中斷重新排序:優先按照中斷的irq_info.class由大到小排列,如果class相同則按照load由大到小排列。
(2) 首先根據中斷所在的numa nodeid將中斷分配到不同的node 節點上。從這里可以看出中斷是不會跨numa 節點遷移的,只能在同一numa node內部進行優化。
(3) 自上而下遍歷各個個拓撲層,對於每一拓撲層:
遍歷該層節點上的中斷,對每一個中斷:
遍歷該節點所有孩子節點,將其遷移到負載最小的孩子節點上。
3.7 activate_mapping
通過修改/proc/irq/[*]/smp_affinity,使處理生效
4. powersave mode
Irqbalance支持powersave mode,默認是關閉的,但是用戶可以通過irqbalance -p <n>來設置閾值power_thresh。開啟該mode,Irqbalance會根據系統內object的負載情況,會自動將某個cpu object切換省電模式/正常模式。
前面3.5小節(1)中,當遍歷cpu層各個對象節點,根據load值計算出cpu平均負載avg_load、標准方差std_deviation后。
(1) 如果load + std_deviation <= avg_load時,表明該cpu的負載較低,則會記錄該cpu object,同時記錄低負載的cpu個數
(2)如果load - std_deviation >= avg_load時,表明該cpu 的負載比較高,也會記錄高負載的cpu個數。
在遍歷完整個cpu層的所有節點后,如果不存在高負載cpu,同時低負載的cpu個數大於設定閾值,則會將最后記錄的cpu對象的powersaved_mode字段置1. 后續在從rebalance_irq_list的鏈表重新分配中斷時,就不會在分配到該cpu上。
一旦系統中存在高負載cpu,irqlabalance就會清除所有cpu 對象的powersaved_mode,恢復正常模式。
5. 補充
1. 中斷再分配時,需要先自上而下遍歷各層各節點,將符合條件的節點上的中斷先遷移到表頭為rebalance_irq_list的鏈表中,然后再將鏈表中的中斷按node->package->cache->cpu逐層分配到各個節點中(分配時會根據irq_info.level決定最終分配到哪一級節點上)。那么剛建立號中斷數據庫時,各個節點上還沒有中斷那么是如何操作的呢?
剛建的中斷數據庫由指真interrupts_db來管理,初次建立好后會將所有的中斷遷移到rebalance_irq_list中。然后將rebalance_irq_list鏈表中中斷按node->package->cache->cpu逐層分配到各個節點中。
2. 如果一個package中有兩個node時,irqbalance的拓撲層是如何划分的?
在這種情況下,cache domian的父節點是package,但是numa節點的子節點是cache domians,而不再是packages,同時package的子節點任然是是cache domians。如圖所示:
歡迎大家批評指正 :-)