负载是查看Linux服务器运行状态时很常用的一个性能指标。|你真的了解linux中的负载吗?( 二 )


内核定义了一个伪文件/proc/loadavg , 每当用户打开这个文件的时候 , 内核中的loadavg_proc_show函数就会被调用到 , 接着访问avenrun全局数组变量并将平均负载从整数转化为小数 , 并打印出来 。
好了 , 另外一个新问题又来了 , avenrun全局数组变量中存储的数据是何时 , 又是被如何计算出来的呢?二、内核中负载的计算过程
接上小节 , 我们继续查看avenrun全局数组变量的数据来源 。 这个数组的计算过程分为如下两步:
1.PerCPU定期汇总瞬时负载:定时刷新每个CPU当前任务数到calc_load_tasks , 将每个CPU的负载数据汇总起来 , 得到系统当前的瞬时负载 。
2.定时计算系统平均负载:定时器根据当前系统整体瞬时负载 , 使用指数加权移动平均法(一种高效计算平均数的算法)计算过去1分钟、过去5分钟、过去15分钟的平均负载 。
接下来我们分成两个小节来分别介绍 。 2.1PerCPU定期汇总负载
在Linux内核中 , 有一个子系统叫做时间子系统 。 在时间子系统里 , 初始化了一个叫高分辨率的定时器 。 在该定时器中会定时将每个CPU上的负载数据(running进程数+uninterruptible进程数)汇总到系统全局的瞬时负载变量calc_load_tasks中 。 整体流程如下图所示 。
负载是查看Linux服务器运行状态时很常用的一个性能指标。|你真的了解linux中的负载吗?
文章图片
我们把上述流程图展开看一下 , 我们找到了高分辨率定时器的源码如下://file:kernel/time/tick-sched.cvoidtick_setup_sched_timer(void){//初始化高分辨率定时器sched_timerhrtimer_init(&ts-sched_timer,CLOCK_MONOTONIC,HRTIMER_MODE_ABS);//将定时器的到期函数设置成tick_sched_timerts-sched_timer.function=tick_sched_timer;}
在高分辨率初始化的时候 , 将到期函数设置成了tick_sched_timer 。 通过这个函数让每个CPU都会周期性地执行一些任务 。 其中刷新当前系统负载就是在这个时机进行的 。 这里有一点要注意一个前提是每个CPU都有自己独立的运行队列 ,。
我们根据tick_sched_timer的源码进行追踪 , 它依次通过调用tick_sched_handle=>update_process_times=>scheduler_tick 。 最终在scheduler_tick中会刷新当前CPU上的负载值到calc_load_tasks上 。 因为每个CPU都在定时刷 , 所以calc_load_tasks上记录的就是整个系统的瞬时负载值 。
我们来看下负责刷新的scheduler_tick这个核心函数://file:kernel/sched/core.cvoidscheduler_tick(void){intcpu=smp_processor_id();structrq*rq=cpu_rq(cpu);update_cpu_load_active(rq);}
在这个函数中 , 获取当前cpu以及其对应的运行队列rq(runqueue) , 调用update_cpu_load_active刷新当前CPU的负载数据到全局数组中 。 //file:kernel/sched/core.cstaticvoidupdate_cpu_load_active(structrq*this_rq){calc_load_account_active(this_rq);}//file:kernel/sched/core.cstaticvoidcalc_load_account_active(structrq*this_rq){//获取当前运行队列的负载相对值delta=calc_load_fold_active(this_rq);if(delta)//添加到全局瞬时负载值atomic_long_add(delta,&calc_load_tasks);}
在calc_load_account_active中看到 , 通过calc_load_fold_active获取当前运行队列的负载相对值 , 并把它加到全局瞬时负载值calc_load_tasks上 。 至此 , calc_load_tasks上就有了当前系统当前时间下的整体瞬时负载总数了 。
我们再展开看看是如何根据运行队列计算负载值的://file:kernel/sched/core.cstaticlongcalc_load_fold_active(structrq*this_rq){longnr_active,delta=0;//R和D状态的用户tasknr_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;}returndelta;}
哦 , 原来是同时计算了nr_running和nr_uninterruptible两种状态的进程的数量 。 对应于用户空间中的R和D两种状态的task数(进程OR线程) 。