0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux中top输出的利用率信息是如何计算出来的?它精确吗?

dyquk4xk2p3d 来源:开发内功修炼 2023-03-25 10:02 次阅读

在线上服务器观察线上服务运行状态的时候,绝大多数人都是喜欢先用 top 命令看看当前系统的整体 cpu 利用率。例如,随手拿来的一台机器,top 命令显示的利用率信息如下:

188ca812-ca69-11ed-bfe3-dac502259ad0.png

这个输出结果说简单也简单,说复杂也不是那么容易就能全部搞明白的。例如:

问题 1:top 输出的利用率信息是如何计算出来的,它精确吗?

问题 2:ni 这一列是 nice,它输出的是 cpu 在处理啥时的开销?

问题 3:wa 代表的是 io wait,那么这段时间中 cpu 到底是忙碌还是空闲?

今天我们对 cpu 利用率统计进行深入的学习。通过今天的学习,你不但能了解 cpu 利用率统计实现细节,还能对 nice、io wait 等指标有更深入的理解。

今天我们先从自己的思考开始!

一、先思考一下

抛开 Linux 的实现先不谈,如果有如下需求,有一个四核服务器,上面跑了四个进程。

18c0ef6e-ca69-11ed-bfe3-dac502259ad0.png

让你来设计计算整个系统 cpu 利用率的这个需求,支持像 top 命令这样的输出,满足以下要求:

cpu 使用率要尽可能地准确;

要尽可能地体现秒级瞬时 cpu 状态。

可以先停下来思考几分钟。

好,思考结束。经过思考你会发现,这个看起来很简单的需求,实际还是有点小复杂的。

其中一个思路是把所有进程的执行时间都加起来,然后再除以系统执行总时间*4。

190caff8-ca69-11ed-bfe3-dac502259ad0.png

这个思路是没问题的,用这种方法统计很长一段时间内的 cpu 利用率是可以的,统计也足够的准确。

但只要用过 top 你就知道 top 输出的 cpu 利用率并不是长时间不变的,而是默认 3 秒为单位会动态更新一下(这个时间间隔可以使用 -d 设置)。我们的这个方案体现总利用率可以,体现这种瞬时的状态就难办了。你可能会想到那我也 3 秒算一次不就行了?但这个 3 秒的时间从哪个点开始呢。粒度很不好控制。

上一个思路问题核心就是如何解决瞬时问题。提到瞬时状态,你可能就又来思路了。那我就用瞬时采样去看,看看当前有几个核在忙。四个核中如果有两个核在忙,那利用率就是 50%。

这个思路思考的方向也是正确的,但是问题有两个:

你算出的数字都是 25% 的整数倍;

这个瞬时值会导致 cpu 使用率显示的剧烈震荡。

比如下图:

192c946c-ca69-11ed-bfe3-dac502259ad0.png

在 t1 的瞬时状态看来,系统的 cpu 利用率毫无疑问就是 100%,但在 t2 时间看来,使用率又变成 0% 了。思路方向是对的,但显然这种粗暴的计算无法像 top 命令一样优雅地工作。

我们再改进一下它,把上面两个思路结合起来,可能就能解决我们的问题了。在采样上,我们把周期定得细一些,但在计算上我们把周期定得粗一些。

我们引入采用周期的概念,定时比如每 1 毫秒采样一次。如果采样的瞬时,cpu 在运行,就将这 1 ms 记录为使用。这时会得出一个瞬时的 cpu 使用率,把它都存起来。

19467f9e-ca69-11ed-bfe3-dac502259ad0.png

在统计 3 秒内的 cpu 使用率的时候,比如上图中的 t1 和 t2 这段时间范围。那就把这段时间内的所有瞬时值全加一下,取个平均值。这样就能解决上面的问题了,统计相对准确,避免了瞬时值剧烈震荡且粒度过粗(只能以 25% 为单位变化)的问题了。

可能有同学会问了,假如 cpu 在两次采样中间发生变化了呢,如下图这种情况。

196a60c6-ca69-11ed-bfe3-dac502259ad0.png

在当前采样点到来的时候,进程 A 其实刚执行完,有一点点时间既没被上一个采样点统计到,本次也统计不到。对于进程 B,其实只开始了一小段时间,把 1 ms 全记上似乎有点多记了。

确实会存在这个问题,但因为我们的采样是 1 ms 一次,而我们实际查看使用的时候最少也是秒级别地用,会包括有成千上万个采样点的信息,所以这种误差并不会影响我们对全局的把握。

事实上,Linux 也就是这样来统计系统 cpu 利用率的。虽然可能会有误差,但作为一项统计数据使用已经是足够了的。在实现上,Linux 是将所有的瞬时值都累加到某一个数据上的,而不是真的存了很多份的瞬时数据。

接下来就让我们进入 Linux 来查看它对系统 cpu 利用率统计的具体实现。

二、top 命令使用数据在哪儿

上一节我们说的 Linux 在实现上是将瞬时值都累加到某一个数据上的,这个值是内核通过 /proc/stat 伪文件来对用户态暴露。Linux 在计算系统 cpu 利用率的时候用的就是它。

整体上看,top 命令工作的内部细节如下图所示。

1990257c-ca69-11ed-bfe3-dac502259ad0.png

top 命令访问 /proc/stat 获取各项 cpu 利用率使用值;

内核调用 stat_open 函数来处理对 /proc/stat 的访问;

内核访问的数据来源于 kernel_cpustat 数组,并汇总;

打印输出给用户态。

接下来我们把每一步都展开来详细看看。

通过使用 strace 跟踪 top 命令的各种系统调用,可以看到它对该文件的调用。

#stracetop
...
openat(AT_FDCWD,"/proc/stat",O_RDONLY)=4
openat(AT_FDCWD,"/proc/2351514/stat",O_RDONLY)=8
openat(AT_FDCWD,"/proc/2393539/stat",O_RDONLY)=8
...

除了 /proc/stat 外,还有各个进程细分的 /proc/{pid}/stat,是用来计算各个进程的 cpu 利用率时使用的。

内核为各个伪文件都定义了处理函数,/proc/stat 文件的处理方法是 proc_stat_operations。

//file:fs/proc/stat.c
staticint__initproc_stat_init(void)
{
proc_create("stat",0,NULL,&proc_stat_operations);
return0;
}

staticconststructfile_operationsproc_stat_operations={
.open=stat_open,
...
};

proc_stat_operations 中包含了该文件对应的操作方法。当打开 /proc/stat 文件的时候,stat_open 就会被调用到。stat_open 依次调用 single_open_size,show_stat 来输出数据内容。我们来看看它的代码:

//file:fs/proc/stat.c
staticintshow_stat(structseq_file*p,void*v)
{
u64user,nice,system,idle,iowait,irq,softirq,steal;

for_each_possible_cpu(i){
structkernel_cpustat*kcs=&kcpustat_cpu(i);

user+=kcs->cpustat[CPUTIME_USER];
nice+=kcs->cpustat[CPUTIME_NICE];
system+=kcs->cpustat[CPUTIME_SYSTEM];
idle+=get_idle_time(kcs,i);
iowait+=get_iowait_time(kcs,i);
irq+=kcs->cpustat[CPUTIME_IRQ];
softirq+=kcs->cpustat[CPUTIME_SOFTIRQ];
...
}

//转换成节拍数并打印出来
seq_put_decimal_ull(p,"cpu",nsec_to_clock_t(user));
seq_put_decimal_ull(p,"",nsec_to_clock_t(nice));
seq_put_decimal_ull(p,"",nsec_to_clock_t(system));
seq_put_decimal_ull(p,"",nsec_to_clock_t(idle));
seq_put_decimal_ull(p,"",nsec_to_clock_t(iowait));
seq_put_decimal_ull(p,"",nsec_to_clock_t(irq));
seq_put_decimal_ull(p,"",nsec_to_clock_t(softirq));
...
}

在上面的代码中,for_each_possible_cpu 是在遍历存储着 cpu 使用率数据的 kcpustat_cpu 变量。该变量是一个 percpu 变量,它为每一个逻辑核都准备了一个数组元素。里面存储着当前核所对应各种事件,包括 user、nice、system、idel、iowait、irq、softirq 等。

在这个循环中,将每一个核的每种使用率都加起来。最后通过 seq_put_decimal_ull 将这些数据输出出来。

19b48c32-ca69-11ed-bfe3-dac502259ad0.png

注意,在内核中实际每个时间记录的是纳秒数,但是在输出的时候统一都转化成了节拍单位。至于节拍单位多长,下一节我们介绍。总之, /proc/stat 的输出是从 kernel_cpustat 这个 percpu 变量中读取出来的。

我们接着再看看这个变量中的数据是何时加进来的。

三、统计数据怎么来的

前面我们提到内核是以采样的方式来统计 cpu 使用率的。这个采样周期依赖的是 Linux 时间子系统中的定时器

Linux 内核每隔固定周期会发出 timer interrupt (IRQ 0),这有点像乐谱中的节拍的概念。每隔一段时间,就打出一个拍子,Linux 就响应之并处理一些事情。

19d393d4-ca69-11ed-bfe3-dac502259ad0.png

一个节拍的长度是多长时间,是通过 CONFIG_HZ 来定义的。它定义的方式是每一秒有几次 timer interrupts。不同的系统中这个节拍的大小可能不同,通常在 1 ms 到 10 ms 之间。可以在自己的 Linux config 文件中找到它的配置。

#grep^CONFIG_HZ/boot/config-5.4.56.bsk.10-amd64
CONFIG_HZ=1000

从上述结果中可以看出,我的机器每秒要打出 1000 次节拍。也就是每 1 ms 一次。

每次当时间中断到来的时候,都会调用 update_process_times 来更新系统时间。更新后的时间都存储在我们前面提到的 percpu 变量 kcpustat_cpu 中。

19e50aec-ca69-11ed-bfe3-dac502259ad0.png

我们来详细看下汇总过程 update_process_times 的源码,它位于 kernel/time/timer.c 文件中。

//file:kernel/time/timer.c
voidupdate_process_times(intuser_tick)
{
structtask_struct*p=current;

//进行时间累积处理
account_process_tick(p,user_tick);
...
}

这个函数的参数 user_tick 指的是采样的瞬间是处于内核态还是用户态。接下来调用 account_process_tick。

//file:kernel/sched/cputime.c
voidaccount_process_tick(structtask_struct*p,intuser_tick)
{
cputime=TICK_NSEC;
...

if(user_tick)
//3.1统计用户态时间
account_user_time(p,cputime);
elseif((p!=rq->idle)||(irq_count()!=HARDIRQ_OFFSET))
//3.2统计内核态时间
account_system_time(p,HARDIRQ_OFFSET,cputime);
else
//3.3统计空闲时间
account_idle_time(cputime);
}

在这个函数中,首先设置 cputime = TICK_NSEC, 一个 TICK_NSEC 的定义是一个节拍所占的纳秒数。接下来根据判断结果分别执行 account_user_time、account_system_time 和 account_idle_time 来统计用户态、内核态和空闲时间。

3.1 用户态时间统计

//file:kernel/sched/cputime.c
voidaccount_user_time(structtask_struct*p,u64cputime)
{
//分两种种情况统计用户态CPU的使用情况
intindex;
index=(task_nice(p)>0)?CPUTIME_NICE:CPUTIME_USER;

//将时间累积到/proc/stat中
task_group_account_field(p,index,cputime);
......
}

account_user_time 函数主要分两种情况统计:

如果进程的 nice 值大于 0,那么将会增加到 CPU 统计结构的 nice 字段中。

如果进程的 nice 值小于等于 0,那么增加到 CPU 统计结构的 user 字段中。

看到这里,开篇的问题 2 就有答案了,其实用户态的时间不只是 user 字段,nice 也是。之所以要把 nice 分出来,是为了让 Linux 用户更一目了然地看到调过 nice 的进程所占的 cpu 周期有多少。

我们平时如果想要观察系统的用户态消耗的时间的话,应该是将 top 中输出的 user 和 nice 加起来一并考虑,而不是只看 user!

接着调用 task_group_account_field 来把时间加到前面我们用到的 kernel_cpustat 内核变量中。

//file:kernel/sched/cputime.c
staticinlinevoidtask_group_account_field(structtask_struct*p,intindex,
u64tmp)
{
__this_cpu_add(kernel_cpustat.cpustat[index],tmp);
...
}

3.2 内核态时间统计

我们再来看内核态时间是如何统计的,找到 account_system_time 的代码。

//file:kernel/sched/cputime.c
voidaccount_system_time(structtask_struct*p,inthardirq_offset,u64cputime)
{
if(hardirq_count()-hardirq_offset)
index=CPUTIME_IRQ;
elseif(in_serving_softirq())
index=CPUTIME_SOFTIRQ;
else
index=CPUTIME_SYSTEM;

account_system_index_time(p,cputime,index);
}

内核态的时间主要分 3 种情况进行统计。

如果当前处于硬中断执行上下文, 那么统计到 irq 字段中;

如果当前处于软中断执行上下文, 那么统计到 softirq 字段中;

否则统计到 system 字段中。

判断好要加到哪个统计项中后,依次调用 account_system_index_time、task_group_account_field 来将这段时间加到内核变量 kernel_cpustat 中。

//file:kernel/sched/cputime.c
staticinlinevoidtask_group_account_field(structtask_struct*p,intindex,
u64tmp)
{
__this_cpu_add(kernel_cpustat.cpustat[index],tmp);
}

3.3 空闲时间的累积

没错,在内核变量 kernel_cpustat 中不仅仅是统计了各种用户态、内核态的使用时间,空闲也一并统计起来了。

如果在采样的瞬间,cpu 既不在内核态也不在用户态的话,就将当前节拍的时间都累加到 idle 中。

//file:kernel/sched/cputime.c
voidaccount_idle_time(u64cputime)
{
u64*cpustat=kcpustat_this_cpu->cpustat;
structrq*rq=this_rq();

if(atomic_read(&rq->nr_iowait)>0)
cpustat[CPUTIME_IOWAIT]+=cputime;
else
cpustat[CPUTIME_IDLE]+=cputime;
}

在 cpu 空闲的情况下,进一步判断当前是不是在等待 IO(例如磁盘 IO),如果是的话这段空闲时间会加到 iowait 中,否则就加到 idle 中。从这里,我们可以看到 iowait 其实是 cpu 的空闲时间,只不过是在等待 IO 完成而已。

看到这里,开篇问题 3 也有非常明确的答案了,io wait 其实是 cpu 在空闲状态的一项统计,只不过这种状态和 idle 的区别是 cpu 是因为等待 io 而空闲。

四、总结

本文深入分析了 Linux 统计系统 CPU 利用率的内部原理。全文的内容可以用如下一张图来汇总:

1a069036-ca69-11ed-bfe3-dac502259ad0.png

Linux 中的定时器会以某个固定节拍,比如 1 ms 一次采样各个 cpu 核的使用情况,然后将当前节拍的所有时间都累加到 user/nice/system/irq/softirq/io_wait/idle 中的某一项上。

top 命令是读取的 /proc/stat 中输出的 cpu 各项利用率数据,而这个数据在内核中是根据 kernel_cpustat 来汇总并输出的。

回到开篇问题 1,top 输出的利用率信息是如何计算出来的,它精确吗?

/proc/stat 文件输出的是某个时间点的各个指标所占用的节拍数。如果想像 top 那样输出一个百分比,计算过程是分两个时间点 t1, t2 分别获取一下 stat 文件中的相关输出,然后经过个简单的算术运算便可以算出当前的 cpu 利用率。

再说是否精确。这个统计方法是采样的,只要是采样,肯定就不是百分之百精确。但由于我们查看 cpu 使用率的时候往往都是计算 1 秒甚至更长一段时间的使用情况,这其中会包含很多采样点,所以查看整体情况是问题不大的。

另外从本文,我们也学到了 top 中输出的 cpu 时间项目其实大致可以分为三类:

一类:用户态消耗时间,包括 user 和 nice。如果想看用户态的消耗,要将 user 和 nice 加起来看才对。

第二类:内核态消耗时间,包括 irq、softirq 和 system。

第三类:空闲时间,包括 io_wait 和 idle。其中 io_wait 也是 cpu 的空闲状态,只不过是在等 io 完成而已。如果只是想看 cpu 到底有多闲,应该把 io_wait 和 idle 加起来才对。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    8944

    浏览量

    85058
  • 定时器
    +关注

    关注

    23

    文章

    3229

    浏览量

    114304
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21604

原文标题:Linux 中 CPU 利用率是如何算出来的?

文章出处:【微信号:良许Linux,微信公众号:良许Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    IcalibrationFSP 0.0002这个值是怎么计算出来的?

    如上述截图(UG-1098 51页),有以下几点不明白: 1)IcalibrationFSP 0.0002这个值是怎么计算出来的,最好说明下推导过程,这个值和ADE9000的动态范围及互感器的比值
    发表于 12-25 08:14

    LTSR 25-NP计算出来的电流误差特别大,为什么?

    大家好,我现在使用的电流传感器是lem的LTSR 25-NP,其中电流和输出电压关系是:v=2.5+0.025*I,现在的AD采样输入范围是0-3V,中间有一个放大倍数,但是即使这样,电压稍微波动一些,计算出来的电流误差就特别大,请大家支招,谢谢!
    发表于 09-03 08:18

    功率设定问题:这个电路输出为7W是如何计算出来的呢?

    【不懂就问】图中,是一个输出为7W的低频功率放大器【1】这个电路输出为7W是如何计算出来的呢?【2】VD1,VD2,VD3所在纵向这一条支路上的器件有什么作用?(VD2和VD3之间是有节点
    发表于 07-13 08:42

    使用ADISIMPLL设计环路滤波器时计算出来的电容值太大如何解决?

    在使用ADISIMPLL时遇到一个问题,由于选用OCXO, Kv很小1E-7左右,设计环路滤波器时计算出来的电容值太大,请问如何解决?
    发表于 09-13 11:02

    请问信噪比是怎么计算出来

    我看到《ADI模数转换器应用笔记》第137页说 这个信噪比是怎么计算出来的呢?
    发表于 09-13 10:54

    这个电阻2.2如何计算出来的,oa ob 的最大电流是多少?

    这个电阻2.2如何计算出来的,oa ob 的最大电流是多少》
    发表于 10-14 00:32

    典型射极跟随器设计的电容值是怎么计算出来的?

    PS:图片上有图,下面的文字是设计过程。已知:音频信号为20Hz~20kHz。静态电流为1mAVcc=+15VVe=+7.5VRe=7.5kΩVb=8.1VR1=130kΩR2=150kΩ问:C1的阻抗Zc=? , C1=? , 是如何计算出来的?
    发表于 05-21 14:53

    直流无刷电机的定子绕组绕多少匝是用什么公式计算出来的?

    请问哪位大神知道直流无刷电机的定子绕组绕多少匝?是用什么公式计算出来的呢?分享一下!
    发表于 04-21 14:47

    PWM的占空比是怎么计算出来的?

    逆变器要使用PWM来控制IGBT生成交流。其他PWM的占空比是怎么计算出来的。
    发表于 08-04 06:53

    请问Calibre电路版图W L是怎么各自计算出来的?

    请问Calibre电路版图W L是怎么各自计算出来的?
    发表于 06-23 08:03

    求累加器B的结果是怎么计算出来的?

    各位大佬,请问图中两个列子累加器B的结果是怎么计算出来的?
    发表于 10-22 22:35

    op1177运放输入阻抗是怎么计算出来的啊?

    上面的电路,反相输入阻抗为100K,而同相输入阻抗为200K,是怎么计算出来的啊?
    发表于 11-28 06:58

    电路是计算出来

    电路教程相关知识的资料,关于电路是计算出来
    发表于 10-10 14:17 0次下载

    Apollo的三个问题和解答控制命令计算出来是发布给谁呢?

    控制命令计算出来是发布给谁呢?ROS吗? 很好的问题,Control_command经过控制模块输出为车辆的控制信息之后,作为与车辆交互的指令,传递给CANBUS模块,由该模块解析为CAN协议报文
    的头像 发表于 07-15 09:54 4276次阅读

    dd马达偏差量怎么计算出来

    反映了电机的实际位置与期望位置之间的差异。 计算DD马达偏差量的基本步骤如下: 确定期望位置:期望位置是系统希望电机达到的目标位置。这可以是用户输入的值,也可以是系统根据某种算法计算出来的值。 获取实际位置:实际位置是电机当前的实际位置。这可
    的头像 发表于 07-11 15:05 501次阅读