Linux中的负载高低和CPU开销并不完全对应
大家好,我是飞哥!
在检查Linux服务器的运行状态时,负载是一个常见的性能指标在观察在线服务器的运行状态时,我们经常会找出负载,看一看当在线请求压力过大时,往往伴伴随着负载的飙升
但是你真的了解载荷的原理吗我来列几个问题,看看你对load的理解够不够深
载荷是如何计算的。
负载和CPU消耗有正相关吗。
内核如何向应用层公开有效载荷数据。
如果你对以上问题的理解不是很准确,那么今天飞哥就带你深入了解一下Linux中的负载!
首先,了解荷载查看流程
我们经常使用top命令来检查Linux系统的负载典型top命令输出的负载如下
#topLoadAvg:1.25,1.30,1.95...........
输出中的负载Avg就是我们常说的负载,也叫系统平均负载因为单一的瞬时载荷值没有太大意义所以Linux计算的是过去一段时间的平均值这三个数字分别代表过去1分钟,过去5分钟和过去15分钟的平均负载值
那么top命令显示的数据数量是怎么来的呢实际上,top命令中的load值来自伪文件/proc/ loadavg通过strace命令跟踪top命令的系统调用可以看出这个过程
#stracetopopenat=7
内核定义了伪文件loadavg的open函数当用户访问/proc/ loadavg时,内核定义的函数将被触发这里会读取内核中的平均负载变量,简单计算后即可显示整个过程如下图所示
我们按照上面的流程图展开让我们看一看伪文件/proc/ loadavg是在内核的/fs/ proc/loadavg.c中定义的..在这个文件中,将创建/proc/ loadavg,并指定操作方法loadavg_proc_fops
//file:fs/proc/load avg . cstaticint _ _ init proc _ load avg _ init proc _ create,0,NULL,ampload avg _ proc _ fops),return0
loadavg_proc_fops包含了打开文件时相应的操作方法。
//file:fs/proc/load avg . cstaticconstructfile _ operationsloadavg _ proc _ fops =。open=loadavg_proc_open,,
在用户模式下打开/proc/ loadavg文件时,调用loadavg_proc_fops中的Open函数指针—loadavg_proc_openLoadavg_proc_open接下来调用loadavg_proc_show进行处理,核心计算在这里完成
//file:fs/proc/load avg . cstaticintloadavg _ proc _ showunsignedlongavnrun(3),//获取平均负载值get _ avenrun (avnrun,fixed _ 1/200,0),//平均打印输出加载seq _ printf (m, "%lu% 02lu% lu
在loadavg_proc_show函数中完成了两件事。
调用get_avenrun来读取当前的负载值
以某种格式打印平均负载值。
在上面的源代码中,你看到了FIXED_1/200,LOAD_INT,LOAD_FRAC等奇怪的定义代码之所以这么琐碎,是因为内核中没有float,double之类的浮点类型,而是用整数模拟的这些代码用于整数和小数之间的转换了解这个背景就好,不要过度分析
这样,用户可以通过访问/proc/ loadavg文件来读取内核计算的负载数据Get_avenrun只是访问avenrun的全局数组
//file:kernel/sched/core . cvoidget _ averunloads(0)=(aven run(0)+offset)lt,移位,loads(1)=(aven run(1)+offset)lt,移位,loads(2)=(aven run(2)+offset)lt,移位,
现在,我们可以在开篇中总结一个问题:内核如何将有效载荷数据暴露给应用层。
内核定义了一个伪文件/proc/ loadavg每当用户打开这个文件,就会调用内核中的loadavg_proc_show函数,然后访问avenrun全局数组变量,将平均负载从整数转换成小数,并打印出来
好了,另一个新问题又来了存储在avenrun全局数组变量中的数据何时以及如何计算
二,内核中负载的计算过程
1.PerCPU定期汇总瞬时负载:定期将各CPU的当前任务计数刷新为calc_load_tasks,汇总各CPU的负载数据,得到系统当前的瞬时负载。
2.定时计算系统的平均负载:定时器根据当前系统的整体瞬时负载,采用指数加权移动平均法计算过去1分钟,过去5分钟和过去15分钟的平均负载。
接下来我们分两节介绍。
2.1 PerCPU定期总结负载
在Linux内核中,有一个子系统叫做时间子系统在时间子系统中,名为高分辨率的计时器被初始化在这个定时器中,每个CPU的负载数据会定期汇总到整个系统的瞬时负载变量calc_load_tasks中整个过程如下图所示
我们来看看上面的流程图。我们发现高分辨率定时器的源代码如下:
//file:kernel/time/tick—sched . cvoidtick _ setup _ sched _ timer//初始化高分辨率定时器sched _ timer _ init(amp,ts—sched_timer,CLOCK_MONOTONIC,HR timer _ MODE _ ABS),//将定时器的到期函数设置为tick _ sched _ time RTS—sched _ timer . function = tick _ sched _ timer,
在高分辨率初始化期间,到期函数设置为tick_sched_timer通过这个函数,每个CPU将周期性地执行一些任务此时,当前系统负载被刷新这里需要注意的一点是,每个CPU都有自己独立的运行队列
我们根据tick_sched_timer的源代码进行追踪,进而调用tick _ sched _ handle = gtupdate _ process _ times = gtscheduler_tick .最终,当前CPU上的负载值将在scheduler_tick中刷新为calc_load_tasks因为每个CPU都是定时刷的,所以整个系统的瞬时负载值都记录在calc_load_tasks上
我们来看看负责刷新的核心函数scheduler_tick:
//file:kernel/sched/core . cvoid scheduler _ tick intcpu = SMP _ processor _ id(),struct rq * rq = CPU _ rq(CPU),更新_ CPU _ load _ active(rq),
该函数获取当前cpu及其对应的运行队列rq,调用update_cpu_load_active将当前cpu的负载数据刷新到全局数组中。
//file:kernel/sched/core . cstaticvoidupdate _ CPU _ load _ active calc _ load _ account _ active(this _ rq),//file:kernel/sched/core . cstaticvidcal _ load _ account _ active//获取当前运行队列的负载相对值delta = calc _ load _ fold _ active(this _ rq),If(delta)//添加到全局瞬时载荷值atomic_long_add(delta,ampcalc _ load _ tasks),
在calc_load_account_active中,当前运行队列的负载相对值是通过calc_load_fold_active获得的,并与全局瞬时负载值calc_load_tasks相加此时,calc_load_tasks拥有当前系统在当前时间的总瞬时负载
让我们展开来看看如何根据运行队列计算负载值:
//file:kernel/sched/core . cstaticlongcalc _ load _ fold _ activelongnr _ active,delta = 0,//处于研发状态的用户任务NR _ active = this _ RQ—NR _ running,NR _ active+=(long)this _ rq—NR _ un interruptible,//如果(nr_active!= this _ rq—calc _ load _ active)delta = NR _ active—this _ rq—calc _ load _ active,this _ rq—calc _ load _ active = NR _ active,returndelta
哦,原来nr_running和nr_uninterruptible状态的进程数是同时计算的用户空间中对应于R和D状态的任务数
因为calc_load_tasks是一个长期存在的数据所以在rq中刷新进程数的时候,只需要刷变化的数量,而不是全部重新计算所以上面的函数返回一个delta
2.2定期计算系统的平均负载。
在上一节中,我们找到了系统的当前瞬时负载calc_load_tasks变量的更新过程现在我们还缺乏一种机制来计算过去1分钟,5分钟,15分钟的平均负载
传统上,当我们计算平均值时,我们总是将过去期间的所有数字相加,然后取平均值把过去N个时间点的所有瞬时载荷加起来取个平均值是不够的其实这是我们传统意义上的平均值如果有n个数字,它们是x1,x2,...,xn那么这个数据集的平均值就是/n
可是,如果使用这种简单的算法来计算平均负载,则存在以下问题:
1.需要存储过去每个采样周期的数据。
假设我们每10毫秒收集一次数据,我们需要使用一个更大的数组来存储每次采样的所有数据,然后我们需要存储1500个数据来统计过去15分钟的平均值而且每出现一个新的观测值,都会从移动平均中减去最早的观测值,加上最新的观测值,内存数组会频繁修改更新
2.计算过程复杂。
计算时,将整个数组相加,除以样本总数虽然加法很简单,但是几百个数的加法还是很繁琐的
3.无法准确表达目前的趋势在传统的平均值计算过程中,所有数字都具有相同的权重但是对于平均负载的实时应用,实际上更接近当前时刻的数值权重应该更大因为它更能反映近期变化的趋势
所以我们在Linux中使用的不是传统的平均值的计算方法,而是指数加权移动平均的一种平均值计算方法。
这种指数加权移动平均方法被广泛应用于深度学习另外,股市中的EMA均线也是用类似的方法求均线的算法的数学表达式为:a1 = a0 *因子+a *这个算法理解起来有点复杂,有兴趣的同学可以谷歌搜索一下
我们只需要知道,这种方法在实际计算中只需要最后一次的平均值,并不需要保存所有的瞬时载荷值另外,离当前时间点越近,权重越高,可以很好的代表最近的趋势
其实这个在时间子系统里也是有规律的做的,三个平均数是用一种叫指数加权移动平均计算的方法计算出来的。
我们来详细看看上图中的执行过程在时钟中断中,子系统会将timer_interrupt注册为时钟中断的处理函数
//file:arch/IA64/kernel/time . cvoid _ _ init time _ init register _ per CPU _ IRQ(IA64 _ TIMER _ VECTOR,amptimer _ IRQ action),ia64 _ init _ ITM(),staticstructirqactiontimer _ IRQ action =handler =定时器_中断,
每次时钟节拍到来,都会调用timer_interrupt,依次调用do_timer函数。
//file:kernel/time/time keying . cvoiddo _ timer calc _ global _ load(ticks),
Calc_global_load是平均负载计算的核心它会获取系统当前的瞬时负载值calc_load_tasks,然后计算过去1分钟,过去5分钟,过去15分钟的平均负载,保存在avenrun中供用户进程读取
//file:kernel/sched/core . cvoidcal _ global _ load//1获取当前瞬时负载值active = atomic _ long _ read(amp,calc _ load _ tasks),//2平均负载的计算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),
获得瞬时负载很简单,只需读取一个内存变量在calc_load中,使用前面提到的指数加权移动平均法计算过去1分钟,过去5分钟和过去15分钟的平均负载
//file:kernel/sched/core . c/* * a1 = A0 * e+a * */staticunsignedlongcalc _ load(unsignedlongload,unsignedlongexp,unsignedlongactive)load * = exp,load+= active *(FIXED _ 1—exp),load+= 1 ullt,lt,(f shift—1),returnloadgt。gt,FSHIFT
虽然这个算法理解起来相当复杂,但是代码看起来确实简单很多,计算量似乎也很小而且不懂也没关系你只需要知道,核不是原来的平均计算方法,而是一种运算速度快,更能表达变化趋势的算法
至此,我们在开头提到了载荷是如何计算的这个问题也有了结论
Linux定期将每个CPU上处于运行队列和不可中断状态的进程数汇总成一个全局系统瞬时负载值,然后定期用指数加权移动平均法统计过去1分钟,过去5分钟和过去15分钟的平均负载。
第三,平均负载和CPU消耗的关系
现在很多同学把平均负载和CPU联系在一起认为负载高时CPU消耗高,负载低时CPU消耗低
在很老版本的Linux中,统计负载的时候,确实只统计可运行任务的数量,这些进程只需要CPU当年的负载和CPU消耗确实是正相关的负载越高,CPU上运行的进程越多,或者等待CPU执行的进程越多,CPU消耗就越高
但正如我们前面看到的,本文使用的3.10版本的Linux负载平均不仅跟踪runnable的任务,还跟踪处于不间断睡眠状态的任务可是,处于不间断状态的进程实际上并不占用CPU
所以高负载一定是CPU处理不过来造成的,也可能是进程调度不了磁盘等资源导致进程进入不可中断状态!
为什么要这样修改我在1993年从网上发来的一封邮件中找到了原因以下是邮件原文
在计算平均负载时,内核只计算可运行的进程我不喜欢这样,问题是快速交换或等待的进程,即不间断的I/O,也会消耗资源当您用慢速交换磁盘替换快速交换磁盘时,平均负载下降似乎有点不直观.....无论如何,下面的补丁似乎使平均负载更符合WRT系统的主观速度最重要的是,当没有人做任何事情时,负载仍然为零
这个补丁提交者的主要思想是,平均负载应该显示对系统所有资源的需求,而不仅仅是对CPU资源的需求。
因此,负载水平表明当前系统对系统资源的整体需求更加严重如果负载变高,可能是CPU资源不够用,或者磁盘IO资源不够用,需要配合其他观察命令来分析具体情况
四。摘要
今天带大家深入研究一下Linux中的负载下面根据一张图总结一下今天学到的内容
我把负载的工作原理分为以下三步。
1.内核定期将每个CPU的负载汇总为系统的瞬时负载。
2.内核使用指数加权移动平均快速计算过去1分钟,5分钟和15分钟的平均值。
3.用户进程通过打开loadavg读取内核中的平均负载。
我们回过头来总结一下开头提到的一些问题。
1.载荷是如何计算的。
它是定期将每个CPU的运行队列中正在运行和不可中断的进程数汇总成一个全局系统瞬时负载值,然后定期用指数加权移动平均法统计过去1分钟,过去5分钟和过去15分钟的平均负载。
2.负载水平和CPU消耗有正相关关系吗。
负载表明当前系统对系统资源的整体需求更加严重如果负载变高,可能是CPU资源不足或磁盘IO资源不足所以不能说看着负载变高,就觉得CPU资源不够用
3.内核如何将有效载荷数据暴露给应用层。
内核定义了一个伪文件/proc/ loadavg每当用户打开这个文件,内核中的loadavg_proc_show函数就会被调用在这个函数中,访问avenrun全局数组变量,将平均负载从整数转换为小数,然后打印出来
郑重声明:此文内容为本网站转载企业宣传资讯,目的在于传播更多信息,与本站立场无关。仅供读者参考,并请自行核实相关内容。