转自: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模式下,处理器被划分成多个”节点”(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 ---清除数据