深刻理解 Linux Load Average

一直不解,为何io占用较高时,系统负载也会变高,偶遇此文,终解吾惑。html

#1 load average介绍

##1.1 load average 指标介绍

uptime和top等命令均可以看到load average指标,从左至右三个数字分别表示1分钟、5分钟、15分钟的load average:java

uptime

16:04:43 up 20 days, 6:08, 2 users, load average: 0.01, 0.00, 0.00

Load average的概念源自UNIX系统,虽然各家的公式不尽相同,但都是用于衡量正在使用CPU的进程数量和正在等待CPU的进程数量,一句话就是runnable processes的数量。因此load average能够做为CPU瓶颈的参考指标,若是大于CPU的数量,说明CPU可能不够用了。linux

可是, Linux上不是这样的!web

Linux上的load average除了包括正在使用CPU的进程数量和正在等待CPU的进程数量以外,还包括uninterruptible sleep的进程数量。一般等待IO设备、等待网络的时候,进程会处于uninterruptible sleep状态。Linux设计者的逻辑是,uninterruptible sleep应该都是很短暂的,很快就会恢复运行,因此被等同于runnable。然而uninterruptible sleep即便再短暂也是sleep,况且现实世界中uninterruptible sleep未必很短暂,大量的、或长时间的uninterruptible sleep一般意味着IO设备遇到了瓶颈。众所周知,sleep状态的进程是不须要CPU的,即便全部的CPU都空闲,正在sleep的进程也是运行不了的,因此sleep进程的数量绝对不适合用做衡量CPU负载的指标,Linux把uninterruptible sleep进程算进load average的作法直接颠覆了load average的原本意义。因此在Linux系统上,load average这个指标基本失去了做用,由于你不知道它表明什么意思,当看到load average很高的时候,你不知道是runnable进程太多仍是uninterruptible sleep进程太多,也就没法判断是CPU不够用仍是IO设备有瓶颈。c#

参考资料:https://en.wikipedia.org/wiki/Load_(computing)数组

“Most UNIX systems count only processes in the running (on CPU) or runnable (waiting for CPU) states. However, Linux also includes processes in uninterruptible sleep states (usually waiting for disk activity), which can lead to markedly different results if many processes remain blocked in I/O due to a busy or stalled I/O system.“网络

##1.2 load_average 的含义

###1.2.1 如何衡量通车大桥的负荷

判断系统负荷是否太重,必须理解load average的真正含义。下面,我根据 “Understanding Linux CPU Load” 这篇文章,尝试用最通俗的语言,解释这个问题。svg

首先,假设最简单的状况,你的电脑只有一个CPU,全部的运算都必须由这个CPU来完成。ui

那么,咱们不妨把这个CPU想象成一座大桥,桥上只有一根车道,全部车辆都必须从这根车道上经过。(很显然,这座桥只能单向通行。)this

系统负荷为0,意味着大桥上一辆车也没有。

图片1

系统负荷为0.5,意味着大桥一半的路段有车。

图片 2

系统负荷为1.0,意味着大桥的全部路段都有车,也就是说大桥已经"满"了。可是必须注意的是,直到此时大桥仍是能顺畅通行的。

图片 3

桥上在通车的时候, 不光桥上的车影响通车的效率, 后面排队等着尚未上桥的也对桥的拥挤程度有贡献, 所以咱们有必要考虑这点, 若是把等待的那些车也算到负载中去, 那么负荷就会 > 1.0.

系统负荷为1.7,意味着车辆太多了,大桥已经被占满了(100%),后面等着上桥的车辆为桥面车辆的70%。以此类推,系统负荷2.0,意味着等待上桥的车辆与桥面的车辆同样多;系统负荷3.0,意味着等待上桥的车辆是桥面车辆的2倍。总之,当系统负荷大于1,后面的车辆就必须等待了;系统负荷越大,过桥就必须等得越久。

图片 4

###1.2.2 类比CPU的系统负荷

CPU的系统负荷,基本上等同于上面的类比。大桥的通行能力,就是CPU的最大工做量;桥梁上的车辆,就是一个个等待CPU处理的进程(process)。

若是CPU每分钟最多处理100个进程,那么系统负荷0.2,意味着CPU在这1分钟里只处理20个进程;系统负荷1.0,意味着CPU在这1分钟里正好处理100个进程;系统负荷1.7,意味着除了CPU正在处理的100个进程之外,还有70个进程正排队等着CPU处理。

为了电脑顺畅运行,系统负荷最好不要超过1.0,这样就没有进程须要等待了,全部进程都能第一时间获得处理。很显然,1.0是一个关键值,超过这个值,系统就不在最佳状态了,你要动手干预了。

###1.2.3 系统负荷的经验法则

1.0是系统负荷的理想值吗?

不必定,系统管理员每每会留一点余地,当这个值达到0.7,就应当引发注意了。经验法则是这样的:

当系统负荷持续大于0.7,你必须开始调查了,问题出在哪里,防止状况恶化。

当系统负荷持续大于1.0,你必须动手寻找解决办法,把这个值降下来。

当系统负荷达到5.0,就代表你的系统有很严重的问题,长时间没有响应,或者接近死机了。你不该该让系统达到这个值。

###1.2.4 多处理器的情形

上面,咱们假设你的电脑只有1个CPU。若是你的电脑装了2个CPU,会发生什么状况呢?

2个CPU,意味着电脑的处理能力翻了一倍,可以同时处理的进程数量也翻了一倍。

仍是用大桥来类比,两个CPU就意味着大桥有两根车道了,通车能力翻倍了。

图片 6

因此,2个CPU代表系统负荷能够达到2.0,此时每一个CPU都达到100%的工做量。推广开来,n个CPU的电脑,可接受的系统负荷最大为n.0。

###1.2.5 多核处理器的情形

芯片厂商每每在一个CPU内部,包含多个CPU核心,这被称为多核CPU。

在系统负荷方面,多核CPU与多CPU效果相似,因此考虑系统负荷的时候,必须考虑这台电脑有几个CPU、每一个CPU有几个核心。而后,把系统负荷除以总的核心数,只要每一个核心的负荷不超过1.0,就代表电脑正常运行。

怎么知道电脑有多少个CPU核心呢?

"cat /proc/cpuinfo"命令,能够查看CPU信息。"grep -c ‘model name’ /proc/cpuinfo"命令,直接返回CPU的总核心数。

-###1.2.6 最佳观察时长

最后一个问题,"load average"一共返回三个平均值----1分钟系统负荷、5分钟系统负荷,15分钟系统负荷,----应该参考哪一个值?

若是只有1分钟的系统负荷大于1.0,其余两个时间段都小于1.0,这代表只是暂时现象,问题不大。

若是15分钟内,平均系统负荷大于1.0(调整CPU核心数以后),代表问题持续存在,不是暂时现象。因此,你应该主要观察"15分钟系统负荷",将它做为电脑正常运行的指标。

#2 Loadavg分析

##2.1 读取 loadavg 的接口 /proc/loadavg

在内核中 /proc/loadavg 是经过 loadavg_proc_show 来读取相应数据,下面首先来看一下load_read_proc的实现:

# https://elixir.bootlin.com/linux/v5.2.13/source/kernel/sched/loadavg.c#L64

/** * get_avenrun - get the load average array * @loads: pointer to dest load array * @offset: offset to add * @shift: shift count to shift the result left * * These values are estimates at best, so no need for locking. */
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{
    loads[0] = (avenrun[0] + offset) << shift;
    loads[1] = (avenrun[1] + offset) << shift;
    loads[2] = (avenrun[2] + offset) << shift;
}

# http://elixir.bootlin.com/linux/v5.2.13/source/fs/proc/loadavg.c#13
static int loadavg_proc_show(struct seq_file *m, void *v)
{
    unsigned long avnrun[3];

    get_avenrun(avnrun, FIXED_1/200, 0);

    seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",
        LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),
        LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),
        LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]),
        nr_running(), nr_threads,
        idr_get_cursor(&task_active_pid_ns(current)->idr) - 1);
    return 0;
}

几个宏的定义以下 :

# https://elixir.bootlin.com/linux/v5.2.13/source/include/linux/sched/loadavg.h#L43



#define FSHIFT 11 /* nr of bits of precision */

#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */

#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */

#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */

#define EXP_5 2014 /* 1/exp(5sec/5min) */

#define EXP_15 2037 /* 1/exp(5sec/15min) */



#define LOAD_INT(x) ((x) >> FSHIFT)

#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)

根据输出格式,LOAD_INT对应计算的是load的整数部分,LOAD_FRAC计算的是load的小数部分。

将a=avenrun[0] + (FIXED_1/200)带入整数部分和小数部分计算可得:

$$avnrun_i = (avnrun_i + \frac{FIXED_1}{200}) <<11

= (avnrun_i+ 1>>11/200) <<11$$

= \frac{avnrun_i + \frac{2{11}}{200}}{2{11}}$$

LOAD_INT(a) = avnrun_i + \frac{2^{11}}{200}

LOAD_FRAC(a) = ((avenrun[0]%(2^11) + 2^11/200) * 100) / (2^11)
             = (((avenrun[0]%(2^11)) * 100 + 2^10) / (2^11)
             = ((avenrun[0]%(2^11) * 100) / (2^11) + ½

由上述计算结果能够看出,FIXED_1/200在这里是用于小数部分第三位的四舍五入,因为小数部分只取前两位,第三位若是大于5,则进一位,不然直接舍去。

临时变量a/b/c的低11位存放的为load的小数部分值,第11位开始的高位存放的为load整数部分。

所以能够获得a=load(1min) * 2^11

所以有: load(1min) * 2^11 = avenrun[0] + 2^11 / 200

进而推导出: load(1min)=avenrun[0]/(2^11) + 1/200

忽略用于小数部分第3位四舍五入的1/200,能够获得load(1min)=avenrun[0] / 2^11,即:

avenrun[0] = load(1min) * 2^11

avenrun是个陌生的量,这个变量是如何计算的,和系统运行进程、cpu之间的关系如何,在第二阶段进行分析。

##2.2 avenrun 如何表示CPU 负载

内核将 load的计算和load的查看进行了分离,avenrun就是用于链接load计算和load查看的桥梁。
下面开始分析经过avenrun进一步分析系统load的计算。

###2.2.1 avenrun 的更新

avenrun 数组是在 calc_global_load 中进行更新, 在系统更新了 calc_load_update 过了 10 个 jiffies 以后, 会在 do_timer 更新 jiffies 以后, 直接调用 calc_global_load 更新 avenrun 数组

do_timer

    -=> calc_global_load

# https://elixir.bootlin.com/linux/v5.2.13/source/kernel/sched/loadavg.c#L337
/* * calc_load - update the avenrun load estimates 10 ticks after the * CPUs have updated calc_load_tasks. * * Called from the global timer code. */

void calc_global_load(unsigned long ticks)

{

    unsigned long sample_window;

    long active, delta;



    sample_window = READ_ONCE(calc_load_update);

    if (time_before(jiffies, sample_window + 10))

        return;



    /* * Fold the 'old' NO_HZ-delta to include all NO_HZ CPUs. */

    delta = calc_load_nohz_fold();

    if (delta)

        atomic_long_add(delta, &calc_load_tasks);



    active = atomic_long_read(&calc_load_tasks);

    active = active > 0 ? active * FIXED_1 : 0;



    avenrun[0] = calc_load(avenrun[0], EXP_1, active);

    avenrun[1] = calc_load(avenrun[1], EXP_5, active);

    avenrun[2] = calc_load(avenrun[2], EXP_15, active);



    WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);



    /* * In case we went to NO_HZ for multiple LOAD_FREQ intervals * catch up in bulk. */

    calc_global_nohz();

}

calc_load_tasks 能够理解为当前系统中 RUNNING(R状态)进程和 uninterruptible(D状态)进程的总数目.

active_tasks为系统中当前贡献load的task数nr_active乘于FIXED_1,用于计算avenrun

avenrun 的计算方法 calc_load 以下所示:

# https://elixir.bootlin.com/linux/v5.2.13/source/include/linux/sched/loadavg.h#L19

/* * a1 = a0 * e + a * (1 - e) */

static inline unsigned long

calc_load(unsigned long load, unsigned long exp, unsigned long active)

{

    unsigned long newload;



    newload = load * exp + active * (FIXED_1 - exp);

    if (active >= load)

        newload += FIXED_1-1;



    return newload / FIXED_1;

}

用avenrun(t-1)和avenrun(t)分别表示上一次计算的avenrun和本次计算的avenrun,则根据CALC_LOAD宏能够获得以下计算:

avenrun(t)=(avenrun(t-1) * EXP_N + nr_active * FIXED_1*(FIXED_1 – EXP_N)) / FIXED_1

= avenrun(t-1) + (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 -EXP_N) / FIXED_1

推导出:

avenrun(t) – avenrun(t-1) = (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 – EXP_N) / FIXED_1

将第一阶段推导的结果代入上式,可得:

(load(t) – load(t-1)) * FIXED_1 = (nr_active – load(t-1)) * (FIXED_1 – EXP_N)

进一步获得nr_active变化和load变化之间的关系式:

load(t) – load(t-1) = (nr_active – load(t-1)) * (FIXED_1 – EXP_N) / FIXED_1

这个式子能够反映的内容包含以下两点:

1)当nr_active为常数时,load会不断的趋近于nr_active,趋近速率由快逐渐变缓

2)nr_active的变化反映在load的变化上是被降级了的,系统忽然间增长10个进程,

1分钟load的变化每次只可以有不到1的增长(这个也就是权重的的分配)。

另外也能够经过将式子简化为:

load(t)= load(t-1) * EXP_N / FIXED_1 + nr_active * (1 - EXP_N/FIXED_1)

这样能够更加直观的看出nr_active和历史load在当前load中的权重关系 (多谢任震宇大师的指出)

#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */ 

#define EXP_5 2014 /* 1/exp(5sec/5min) */ 

#define EXP_15 2037 /* 1/exp(5sec/15min) */

1分钟、5分钟、15分钟对应的EXP_N值如上,随着EXP_N的增大,(FIXED_1 – EXP_N)/FIXED_1值就越小,

这样nr_active的变化对总体load带来的影响就越小。对于一个nr_active波动较小的系统,load会

不断的趋近于nr_active,最开始趋近比较快,随着相差值变小,趋近慢慢变缓,越接近时越缓慢,并最

终达到nr_active。以下图所示:

文件:load 1515.jpg(无图)

也所以获得一个结论,load直接反应的是系统中的nr_active。 那么nr_active又包含哪些? 如何去计算

当前系统中的nr_active? 这些就涉及到了nr_active的采样。

###2.2.2 calc_load_tasks 的更新


calc_load_tasks 常规状况下在 tick 中进行更新.

this_rq->calc_load_active 记录了当前 RQ 上 RUNNING(R状态)线程和 uninterruptible(D状态)线程的总数.

delta 为上次更新到如今 this_rq 上 R+D 状态进程的增量状况.

calc_load_tasks 则保存了当前系统中进程 R+D 状态进程的总数目.

LOAD_FREQ 被定义成 5HZ+1(5S 以后), 是更新 this_rq->calc_load_active 和 全局的 calc_load_tasks 的时间间隔, 每 LOAD_FREQ 才会更新一次.

this_rq->calc_load_update 总表示当前RQ 上下次执行 calc_global_load_tick 时能够进行更新的时间

在 calc_global_load_tick 中

  1. 先检查 calc_load_update 到期没,

  2. 到期后,

先更新了 this_rq->calc_load_active

接着更新了全局的 calc_load_tasks.

最后设置 this_rq->calc_load_update 为 LOAD_FREQ(5S) 以后.

所以每隔 LOAD_FREQ的时间, 系统在calc_global_load_tick 中基于 this_rq->calc_load_active, 更新 全局的calc_load_tasks.

# https://elixir.bootlin.com/linux/v5.2.13/source/kernel/sched/loadavg.c#L79

static long calc_load_fold_active(struct rq *this_rq)

{

        long nr_active, delta = 0;

 

        nr_active = this_rq->nr_running;

        nr_active += (long) this_rq->nr_uninterruptible;

 

        if (nr_active != this_rq->calc_load_active) {

                delta = nr_active - this_rq->calc_load_active;

                this_rq->calc_load_active = nr_active;

        }

 

        return delta;

}



# https://elixir.bootlin.com/linux/v5.2.13/source/kernel/sched/loadavg.c#L369

/* * Called from scheduler_tick() to periodically update this CPU's * active count. */

void calc_global_load_tick(struct rq *this_rq)

{

    long delta;



    if (time_before(jiffies, this_rq->calc_load_update))

        return;



    delta = calc_load_fold_active(this_rq, 0);

    if (delta)

        atomic_long_add(delta, &calc_load_tasks);



    this_rq->calc_load_update += LOAD_FREQ;

}

而 avenrun 则在 calc_load_update 更新 10 ticks 以后经过 calc_global_load 更新.

#3 参考资料

https://www.cnblogs.com/qqmomery/p/6267429.html

http://linuxperf.com/?p=176

https://scoutapm.com/blog/understanding-load-averages

http://www.ruanyifeng.com/blog/2011/07/linux_load_average_explained.html

http://www.blogjava.net/cenwenchu/archive/2008/06/30/211712.html

https://www.cnblogs.com/qqmomery/p/6267429.html