irqbalance 是什么?项目主页上有以下描述:
Irqbalance is a daemon to help balance the cpu load generated by interrupts across all of a systems cpus.
它避免了单 cpu 负载过重情况的出现。用法如下:
root@a7661ef9b2f8 test]# irqbalance -h
irqbalance: option requires an argument -- 'h'
irqbalance [--oneshot | -o] [--debug | -d] [--foreground | -f] [--hintpolicy= | -h [exact|subset|ignore]] [--banscript= | -b <script>]
[--powerthresh= | -p <off> | <n>] [--banirq= | -i <n>] [--policyscript= | -l <script>] [--pid= | -s <file>] [--deepestcache= | -c <n>]
# 查看当前运行情况
service irqbalance status
# 终止服务
service irqbalance stop
首先有一些前置知识需要说明,这涉及到 irqbalance cputree 的分层。
前置知识
中断
每个硬件设备都需要和 CPU 有某种形式的通信以便 CPU 及时知道发生了什么,这样 CPU 可能就会放下手中的事情去处理应急事件,硬件设备主动打扰 CPU 的现象就可称为硬件中断。就像正在一心一意的写代码时,突然钉钉“噔噔”地响起来,这时我们就知道有事情需要处理,这里的“噔噔”声就可以理解成一次中断。
CPU 和硬件沟通的方式中,还有一种叫做轮询(polling),就是让 CPU 定时对硬件状态进行查询然后做相应处理,这比较浪费 CPU,属于一种硬件被动的方式。相比下来,硬件主动的方式(中断)更有效一些。
那每个硬件设备都有中断,很简单啊,给它们分个唯一的号码,也就是 irq 号,在 /proc/interrupts
文件中的第一列可以看到所有的irq。
只有 kernel 2.4 以后的版本才支持的把不同的硬件中断请求(IRQs)分配到特定的 CPU 上的绑定技术被称为 SMP IRQ Affinity,这个后面还会详细说。
NUMA架构
简要介绍一下 NUMA 架构。
NUMA 架构出现前,CPU 频率一路欢脱越来越高,直至碰到物理极限的天花板,后转向核数越来越多的方向发展。
如果每个 core 的工作性质都是 share-nothing(类似于map-reduce的node节点的作业属性),那么也许就不会有NUMA。由于所有CPU Core都是通过共享一个北桥来读取内存,随着核数如何的发展,北桥 在响应时间上的性能瓶颈越来越明显。于是,聪明的硬件设计师们,先到了把内存控制器(原本北桥中读取内存的部分)也做个拆分,平分到了每个die上。于是 NUMA 就出现了!
NUMA 架构中,内存访问有远近之分,只有当 CPU 访问自身直接 attach 内存对应的物理地址时,才会有较短的响应时间(Local Access)。而如果需要访问其他 CPU attach 的内存的数据时,就需要通过 inter-connect 通道访问,响应时间就相比之前变慢了(Remote Access),NUMA(Non-Uniform Memory Access)就此得名。 --- 引自 http://cenalulu.github.io/lin...
图画下来大概是下面这个样子:
numactl --hardware
命令可以查看的那个机器的 numa 拓扑,比如这台机器:
[root@d2b9eb755bb1 ~]# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 24 25 26 27 28 29 30 31 32 33 34 35
node 0 size: 130946 MB
node 0 free: 9892 MB
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 131072 MB
node 1 free: 35969 MB
node distances:
node 0 1
0: 10 21
1: 21 10
或者用这个脚本也行:
[root@d2b9eb755bb1 ~]# for i in `ls /sys/devices/system/node | grep node`;do echo -ne "$i\t";cat /sys/devices/system/node/$i/cpulist;done
node0 0-11,24-35
node1 12-23,36-47
或者用 lscpu
这个命令,
[root@d2b9eb755bb1 ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 48
On-line CPU(s) list: 0-47
Thread(s) per core: 2
Core(s) per socket: 12
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 79
Stepping: 1
CPU MHz: 2197.264
BogoMIPS: 4401.60
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 30720K
NUMA node0 CPU(s): 0-11,24-35
NUMA node1 CPU(s): 12-23,36-47
CPU 相关
cpu cache 结构图如下:
从硬件的角度,上图的 L1 和 L2 Cache 都被两个 HT 共享,且在同一个物理 Core。而 L3 Cache 则在物理 CPU 里,被多个 Core 来共享。 而从 OS 内核角度,每个 HT 都是一个逻辑 CPU。
以 cpu0 为例,如下:
[root@d2b9eb755bb1 ~]# tree -L 1 /sys/devices/system/cpu/cpu0/cache/
/sys/devices/system/cpu/cpu0/cache/
├── index0 -> L1 data缓存
├── index1 -> L1 Instruction缓存
├── index2 -> L2 缓存
└── index3 -> L3 缓存
点到为止,想了解更多可以翻翻以前的课本。更多 cpu 信息可以从 /proc/cpuinfo
文件中获取到。
irq 亲缘绑定
下面基于实践简单说下这个事情。/proc/interrupts
文件中可以看到各个 cpu 上的中断情况。/proc/irq/#/smp_affinity_list
可以查看指定中断当前绑定的 CPU,当然也 可以看 smp_affinity
这个文件,它是一个16进制bitmask,以逗号分隔,比如 0000,00000020
表示该 irq 分给了 CPU5。
所以,通过如下脚本获得各网卡中断的当前 cpu 的整体情况(平时只对网卡中断感兴趣):
cat /proc/interrupts | grep eth0- | cut -d: -f1 | while read i; do echo -ne irq":$i\t bind_cpu: "; cat /proc/irq/$i/smp_affinity_list; done | sort -n -t' ' -k3
效果大约是这样的:
irq:113 bind_cpu: 0
irq:117 bind_cpu: 1
irq:136 bind_cpu: 2
irq:109 bind_cpu: 3
irq:137 bind_cpu: 4
irq:106 bind_cpu: 5
irq:112 bind_cpu: 6
irq:111 bind_cpu: 7
irq:115 bind_cpu: 8
irq:149 bind_cpu: 8
irq:152 bind_cpu: 8
irq:133 bind_cpu: 9
irq:110 bind_cpu: 10
irq:114 bind_cpu: 11
irq:130 bind_cpu: 24
irq:148 bind_cpu: 24
irq:131 bind_cpu: 25
irq:139 bind_cpu: 26
irq:118 bind_cpu: 27
irq:132 bind_cpu: 27
irq:123 bind_cpu: 28
irq:128 bind_cpu: 28
irq:134 bind_cpu: 28
irq:142 bind_cpu: 28
irq:150 bind_cpu: 28
irq:135 bind_cpu: 29
irq:108 bind_cpu: 30
irq:116 bind_cpu: 31
irq:119 bind_cpu: 32
irq:124 bind_cpu: 32
irq:126 bind_cpu: 32
irq:127 bind_cpu: 32
irq:138 bind_cpu: 32
irq:151 bind_cpu: 32
irq:107 bind_cpu: 33
irq:121 bind_cpu: 34
irq:140 bind_cpu: 34
irq:120 bind_cpu: 35
irq:122 bind_cpu: 35
irq:125 bind_cpu: 35
irq:129 bind_cpu: 35
irq:141 bind_cpu: 35
irq:143 bind_cpu: 35
irq:144 bind_cpu: 35
irq:145 bind_cpu: 35
irq:146 bind_cpu: 35
irq:147 bind_cpu: 35
irq:153 bind_cpu: 35
可以看到,我这台机器有一半cpu 是空闲的,已经绑定的 cpu 绑定的 irq 也不太均衡。
假如要更改的话,可以有如下类似的操作 echo 3 > /proc/irq/24/smp_affinity
。
这个也是后面 irqbalance 用来调整中断的方法。
irqbalance 代码分析
下面以 v1.07 为例来进行分析。
irqbalance 中把中断分成了 8 种 class, 4 种 type。
/*
* IRQ Classes
*/
#define IRQ_OTHER 0
#define IRQ_LEGACY 1
#define IRQ_SCSI 2
#define IRQ_VIDEO 3
#define IRQ_ETH 4
#define IRQ_GBETH 5
#define IRQ_10GBETH 6
#define IRQ_VIRT_EVENT 7
/*
* IRQ Types
*/
#define IRQ_TYPE_LEGACY 0
#define IRQ_TYPE_MSI 1
#define IRQ_TYPE_MSIX 2
#define IRQ_TYPE_VIRT_EVENT 3
为啥是 8 种 class 呢?这个是依据 pci 设备初始化时注册的类型,可以通过以下脚本来查看
[root@d2b9eb755bb1 ~]# for i in `ls /sys/bus/pci/devices/*/class`;do echo $(( `cat $i` >> 16));done | sort -nu | wc -l
8
以上的 class 对应 IRQ Classes 使用如下的数组:
static short class_codes[MAX_CLASS] = {
IRQ_OTHER,
IRQ_SCSI,
IRQ_ETH,
IRQ_VIDEO,
IRQ_OTHER,
IRQ_OTHER,
IRQ_LEGACY,
IRQ_OTHER,
IRQ_OTHER,
IRQ_LEGACY,
IRQ_OTHER,
IRQ_OTHER,
IRQ_LEGACY,
IRQ_ETH,
IRQ_SCSI,
IRQ_OTHER,
IRQ_OTHER,
IRQ_OTHER,
};
MAX_CLASS = 0x12
即 18。
不同 class 的中断平衡的时候作用域不同,有的在PACKAGE,有的在CACHE,有的在CORE。这个关系对应依靠以下数组进行转换:
int map_class_to_level[8] =
{ BALANCE_PACKAGE, BALANCE_CACHE, BALANCE_CORE, BALANCE_CORE, BALANCE_CORE, BALANCE_CORE, BALANCE_CORE, BALANCE_CORE };
irqbalance 会根据cpu的结构由上到下建立了一个树形结构,最顶层是 numa_nodes,向下以此为 CPU packages、Cache domains以及CPU cores,自顶向下。
irqbalance 的主函数很简单,10s 一个周期,做以下事情:
【1】清除上次统计结果
【2】分析中断情况
【3】分析中断的负载情况
【4】计算如何平衡中断
【5】实施上面指定的方案
// irqbalance.c
int main(int argc, char** argv) {
// ...
// ...
while (keep_going) {
sleep_approx(SLEEP_INTERVAL); // 10s
clear_work_stats();
parse_proc_interrupts();
parse_proc_stat();
// ...
// ...
calculate_placement();
activate_mappings();
// ...
}
// ...
}
中断最终是运行在某一个cpu上的,所以有的中断虽然分配在cache、package层次上,但是最终还是在cpu上运行,所有每个cpu执行中断数大概等于所有父节点的中断数一级一级平均下来。然后用该cpu的负载除以该cpu平均处理的中断数,得到单位中断所占用的负载,那么每个中断的负载就等于该中断在单位时间内新增的个数乘以单位中断所占用的负载。那问题来了,如何计算负载的呢?
答案是通过/proc/stat
文件的 irq + softirq 获得的,以 cpu0 为例,一个可能的数据如下:
cpu0 200118431 1258 112897097 1062445972 321829 0 1048436 0 0 0
以上的数组表示从系统启动开始累计到当前时刻的 jiffies数(jiffies 是内核中的一个全局变量,用来记录自系统启动一来产生的节拍数,在linux中,一个节拍大致可理解为操作系统进程调度的最小时间片,不同linux内核可能值有不同,通常在1ms到10ms之间)。
以上各字段的含义如下表:
数值 | 参数 | 含义 |
---|---|---|
200118431 | user | 处于用户态的运行时间,不包含 nice值为负进程。 |
1258 | nice | nice值为负的进程所占用的CPU时间 |
112897097 | system | 处于核心态的运行时间 |
1062445972 | idle | 除IO等待时间以外的其它等待时间 |
321829 | iowait | IO等待时间(since 2.5.41) |
0 | irq | 硬中断时间 |
1048436 | softirq | 软中断时间 |
0 | steal | - |
0 | guest | - |
0 | guest_nice | - |
具体可以看 /proc 目录详解。
所以,cpu->last_load = (irq_load + softirq_load)
。
每个CORE的负载是附在上面的中断的负载的总和,
每个DOMAIN是包含的CORE的总和,
每个PACKAGE包含的DOMAIN的总和,就像树层次一样的计算。
关于如何平衡上面得到的 load 值呢?下一篇再做讲解。
To be continued...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。