irqbalance详解(其一)

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…

图画下来大概是下面这个样子:
《irqbalance详解(其一)》

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 结构图如下:
《irqbalance详解(其一)》
从硬件的角度,上图的 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之间)。
以上各字段的含义如下表:

数值参数含义
200118431user处于用户态的运行时间,不包含 nice值为负进程。
1258nicenice值为负的进程所占用的CPU时间
112897097system处于核心态的运行时间
1062445972idle除IO等待时间以外的其它等待时间
321829iowaitIO等待时间(since 2.5.41)
0irq硬中断时间
1048436softirq软中断时间
0steal
0guest
0guest_nice

具体可以看 /proc 目录详解
所以,cpu->last_load = (irq_load + softirq_load)
每个CORE的负载是附在上面的中断的负载的总和,
每个DOMAIN是包含的CORE的总和,
每个PACKAGE包含的DOMAIN的总和,就像树层次一样的计算。

关于如何平衡上面得到的 load 值呢?下一篇再做讲解。

To be continued…

    原文作者:happen
    原文地址: https://segmentfault.com/a/1190000015426246
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞