代表中断的分配范围,不急,接着看一下具体的分配方式: 首先是中断在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 ---清除数据