我们常常会使用top命令来查看系统的性能情况,在top命令的第一行可以看见loadaverage这个数据,如右图所示:
loadaverage包含3列linux 系统平均负载linux源代码分析,分别表示1分钟、5分钟和15分钟的系统平均负载。
对于系统平均负载这个数值,可能好多朋友并不完全理解其意义,并不晓得数值达到多少时才表示系统负载过低。本文将会以简单的语言来介绍系统平均负载这个概念,但是会介绍Linux内核是如何估算这个数值。
系统平均负载
《UnderstandingLinuxCPULoad(链接在文章最后)》这篇文章早已十分浅显的解释了哪些是系统平均负载,这儿借用一下此文中的事例。
若果将CPU称作是桥梁,对于单核的CPU就好比是自行车道的桥梁。每次桥梁只能让一辆车辆通过,但是要以规定的速率通过。这么:
系统的平均负载与前面的事例一样,在单核CPU的环境下:
对于单核CPU来说,平均负载1.0表示使用率最高。但对于多核CPU来说,平均负载要减去核心数。诸如在4核CPU的系统中,当平均负载为4.0时,才表示CPU的使用率最高。
Linux平均负载估算原理
在介绍系统平均负载的估算原理前,先要介绍一下哪些是系统负载。在Linux系统中,系统负载表示系统中当前正在运行的进程数目,其包括可运行状态的进程数和不可中断休眠状态的进程数的和。注意:不可中断休眠状态的进程通常是在等待I/O完成的进程。
系统负载 = 可运行状态进程数 + 不可中断休眠状态进程数
复制
晓得了哪些是系统负载,这么系统平均负载就容易理解了。例如每5秒统计一次系统负载,1分钟内会统计12次。如下所示:
第5秒 -> 系统负载
第10秒 -> 系统负载
第15秒 -> 系统负载
...
第60秒 -> 系统负载
复制
之后把每次统计到的系统负载加上去,再减去统计次数,即可得出系统平均负载。如右图所示:
但这些估算方法有些缺陷,就是预测系统负载的确切性不够高,由于越老的数据越不能反映现今的情况。打个比方,要预测某条道路明天的车流量,使用今天的数据作为预测根据,会比使用一个月之前的数据作为根据要确切得多。
所以,时间越近的数据,对未来的预测确切性越高。
Linux内核使用一种名为指数平滑法的算法来解决这个问题,指数平滑法的核心思想是对新老数据进行加权,越老的数据权重越低。
指数平滑法:是由RobertG..Brown提出的一种加权联通平均法,有兴趣了解其物理原理的可以搜索相关资料,本文不作详尽介绍。
其估算公式如下(来始于Linux内核代码kernel/sched/core.c):
load1 = load0 * e + active * (1 - e)
复制
解释一下里面公式的意思:
所以,我们就可以使用前面的公式来预测任何时间的系统平均负载了。例如,我们要预测时间点n的系统平均负载,这么可以这样来估算:
load1 = load0 * e + active * (1 - e)
load2 = load1 * e + active * (1 - e)
load3 = load2 * e + active * (1 - e)
...
loadn = loadn-1 * e + active * (1 - e)
复制
如今就只剩下衰减系数该怎么估算了。
从Linux内核的注释可以了解到,估算1分钟内系统平均负载的衰减系数的估算方法如下:
1 / exp(5sec / 1min)
复制
其中:
也就是说,要估算一分钟的系统平均负载时,须要使用前面的衰减系数。对于5分钟和15分钟的衰减系数的估算方法分别为:
1 / exp(5sec / 5min)
1 / exp(5sec / 15min)
复制
Linux内核早已把1分钟、5分钟和15分钟的衰减系数结果估算下来,而且定义在include/linux/sched.h文件中,如下所示:
#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) */
复制
通过上述公式估算下来的衰减系数是个浮点数linux 系统平均负载,而在内核中是不能进行浮点数运行的。解决方式是先对衰减系数进行扩大linux软件工程师培训,之后在展示时最缩小。所以,里面的衰减系数数值是经过扩大2048倍后的结果。
Linux平均负载估算实现
万事俱备,只欠东风。前面我们早已把所有的知识点介绍了,如今来剖析一下Linux内核代码是如何实现的。
1.数据储存
在Linux内核中,使用了avenrun链表来储存1分钟、5分钟和15分钟的系统平均负载,如下代码所示:
unsigned long avenrun[3];
复制
如元素avenrun[0]用于储存1分钟内的系统平均负载,而元素avenrun[1]用于储存5分钟的系统平均负载,这么类推。
2.统计过程
因为统计须要定时进行,所以内核把统计过程放置到时钟中断中进行。当时钟中断触发时,将会调用do_timer()函数,而do_timer()函数将会调用calc_global_load()来统计系统平均负载。
我们来瞧瞧calc_global_load()函数的实现:
void calc_global_load(unsigned long ticks)
{
long active, delta;
// 1. 如果还没到统计的时间间隔,那么将不进行统计(5秒统计一次)
if (time_before(jiffies, calc_load_update + 10))
return;
// 2. 获取活跃进程数
delta = calc_load_fold_idle();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
// 3. 统计各个时间段系统平均负载
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);
// 4. 更新下次统计的时间(增加5秒)
calc_load_update += LOAD_FREQ;
...
}
复制
calc_global_load()函数主要完成4件事情:
判定当前时间是否须要进行统计,假如还没到统计的时间间隔,这么将不进行统计(5秒统计一次)。获取活跃进程数(可运行状态进程数+不可中断休眠状态进程数)。统计各个时间段系统平均负载(1分钟、5分钟和15分钟)。更新上次统计的时间(降低5秒)。
从前面的剖析可知,calc_global_load()函数将会调用calc_load()来估算系统平均负载。其代码如下:
/*
* a1 = a0 * e + a * (1 - e)
*/
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
load *= exp;
load += active * (FIXED_1 - exp);
load += 1UL <> FSHIFT;
}
复制
calc_load()函数的各个参数意义如下:
可以看出,calc_load()函数的实现就是根据指数平滑法来估算的。
参考文献:
文章评论