top工具全字段解析+实战(一)

一、top的概念

top是linux下一个用户态工具,和windows下任务管理器差不多,就是实时显示linux系统运行状态,进程线程cpu占用,内存使用情况等等。可以实时动态地查看系统的整体运行情况,是一个综合了多方信息检测系统性能和运行信息的实用工具。

二、top在哪里获取?

(1)如果是发行版的linux,top由procps组件提供,procps是内核proc utils工具集,其中不仅包含了top命令,还有我们熟知的ps,kill,free等命令都源于procps组件,CGEL6x的yocto downloads目录下也有procps,可以解开看看。

(2)如果是嵌入式linux,一般由busybox提供,相对比服务器版的top,功能会偏弱一些。a tiny top

三、top的版本

先来看下服务器上的top,命令行下敲打:top -v

-bash-4.2$ top -v
procps-ng version 3.3.9
Usage:
top -hv | -bcHiOSs -d secs -n max -u|U user -p pid(s) -o field -w [cols]
-bash-4.2$

       从输出信息中,我们可以看出top的版本 其实就是procps-ng的版本,版本号为:3.3.9,后面为啥带个ng,啥意思,我们顺带随追溯下procps工具的历史。

       最开始procps工具的maintainer很少有时间去处理procps,不知道为啥,反正一开始procps项目维护的并不好,在1997年的时候,Albert Cahalanprocps包写了一个新的ps程序,但随后几年里,Albert Cahalan一直悄悄的帮助debian packet maintainer修复bug,直到2001年,因为缺少维护者,Rik van Riel决定要为procps搞点事情,他在红帽公司的CVS上将旧的代码重新捡起来,并开始添加补丁,与此同时,一些开发者也以不同的形式开始添加patch

到了2002年,Albert Cahalan把procps项目放到了http://procps.sourceforge.net,并设置procps的主版本号为3,一部分原因是他不想遗失之前的功能测试和bug fix,另一方面原因是top的源码已经被重写了,

后来procps.sourceforge.net停了,项目移动到了http://gitorious.org/procps,http://gitlab.com/procps-ng/procps,这个时候,Debian,Fedora,openSUSE开始fork这个项目,此时procps项目被重新命名为procps-ng(next-generation),版本号变更成了3.3.0,同时库的名称由libproc.so变成libprocps.so。

 

OK,ng就是下一代的意思,瞬间似乎明白了很多软件包的含义,libcap-ng,netsniff-ng

那top这个名字是啥意思?高高的 ? 顶部? 和任务管理器有啥关系,实际上是table of processes,主要是用来描述进程的。展示进程的自身属性及资源使用情况,如cpu占用,内存占用,运行状态。本文就是这么专!不放过一个技术细节,即使是程序命名也要追溯其来源、含义!

 

四、top原理?

就是借助于操作系统内核暴露出来的proc接口,top程序来进行数据统计,并以终端形式进行展示。

主要接口:/proc/pid/stat、/proc/stat、/proc/uptime、/proc/meminfo

 

五、Top全字段分析

好了,说了一大堆无关紧要的,直奔主题吧,看一下top的输出格式及字段详解,并以实际程序来动态调整每个字段的输出结果,直观感受下各字段的作用,眼见为实。

先来个top全景:

各部分含义

①系统时间字段

图中字段含义:系统时间,当前时间为14:03:20

系统时间,可以通过date -s命令来修改。如:date -s 10:10:10

系统时间设置与读取分别由syscall(time)和syscall(settimeofday)系统调用来设置,并通过/etc/localtime文件进行时区转换。

修改一把:

 

[root@localhost top]# date -s 04:33:52

Fri Jun  8 04:33:52 CST 2018

来观察下top:

 

还原当前时间:

 

[root@localhost top]# date -s 14:34:26

Fri Jun  8 14:34:26 CST 2018

在观察下top效果:

 

 

②系统运行时间字段

 

图中字段含义:系统从上电运行时长为14天15小时,这期间未发生断电。

系统运行时间由/proc/uptime提供,这个时间的变动不好做实验,因为这个时间是只读的,本身就不应该让人为去设置。据说这个程序,可以通过获取内核的uptime_proc_fops结构体地址,然后进行数据篡改。更改uptime的运行时间,制造假象!这个hack行为就暂时不做实验了。

https://github.com/dkorunic/uptime_hack/blob/master/uptime_hack.c

 

③当前系统用户数字段

 

图中字段含义:当前有84个用户。

来个实验:

新建一个ssh链接到服务器,ssh yuchen@10.74.154.170,可以看到top中user会增加一个。

 

将ssh终端断开连接:在看下top:

 

实际上users用户数的统计由/var/run/utmp给出数据。

 

④系统负载

 

图中字段含义:3个字段分别表示系统最后1分钟,5分钟,15分钟,系统负载情况,数值越高说明负载越严重。

 因为给出了过去时刻的3个字段,如果你看到1分钟的负载较高,但是15分钟的负载并不高,那么可能代表有一小段时间在进行cpu密集型计算,系统宏观上还是正常的,loadavg可作为系统忙时调试的一种数据参考。

 

来模拟下高负载:

创建256个任务同时跑,观察top情况。

先看一下空闲时,系统top情况:可以看到基本上都是0.x,基本都是空闲状态,运行良好。

程序代码:(这段代码用于创建loop程序,后面会经常用到)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#define MAX_PTHREAD_NUM 256
void pthread_func(void *args)
{
    printf("create pthread task! pid: %d\n", syscall(__NR_gettid));
    while(1);
}
int main(int argc, char **argv)
{
    int i, n = 1;
    pthread_t tid[MAX_PTHREAD_NUM];
    if(argc == 2)
        n = atoi(argv[1]);
    for(i=0; i<n; i++)
        pthread_create(&tid[i], NULL, (void*)pthread_func, NULL);
    for(i=0; i<n; i++)
        pthread_join(tid[i], NULL);
    while(1);
    return 0;
}

创建256个任务,启动程序,观察top:

[root@localhost top]# ./a.out 256

reate pthread task! pid: 18245

create pthread task! pid: 18106

create pthread task! pid: 18137

………………

………………

create pthread task! pid: 18237

create pthread task! pid: 18241

 

 随着时间的推移,loadavg的值开始升高,1分钟的loadavg增长最快,5分钟loadavg增长缓慢,15分钟loadavg增长最慢。

 

按下大H,展开线程,可以看到系统中有大量任务在运行。

 

运行了2分钟的测试程序。Loadavg的值已经达到9.x, 5.x, 2.x,Top如下:

 

停止测试程序,loadavg开始缓慢降低。一分钟loadavg下降最快,5分钟其次,15分钟下降最慢。

 

一分钟过后,loadavg值已经变成了0.x

 

Ok,loadavg就是这么个东西,loadavg显示了1,/5/15分钟系统负载情况。

 

另外,在busybox中的top,loadavg的显示这样的:

 

Loadavg后面多出2个字段,2/135 和 1168

2表示:当前系统中运行的任务数,(R)。

135表示:系统总任务数。

1168:系统最近一次启动的任务pid

 

⑤Tasks行表示系统任务情况,(分别代表:任务总数,正在运行任务数,睡眠任务数,停止任务数,僵尸任务数)

 

 图中字段依次含义:任务总数,正在运行任务数,睡眠任务数,停止任务数,僵尸任务数。可以看到总共有320个任务,1个任务运行,319个睡觉,0个停止,0个僵尸。

 

运行任务(R):获取cpu在执行。

睡眠任务(S/D):等待资源或信号到来,睡觉。

停止任务(T):任务处于stop状态,发送continue signal可以让其继续运行。

僵尸任务(Z):任务已经挂了,其父进程未收尸。

 

进程状态之--→ R

R:事实上,在内核态中,挂在cpu的运行队列上的任务都是R状态。但是处于R状态并不代表你拿到了cpu控制权。运行任务可能有多个,但是实实在在能在cpu运行的只有一个(单核系统)。所以,R任务代表你所有资源都已具备,时刻等待cpu调度。但也可能你连cpu都拿到了,那你就是真正的running!!

 

测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
 
#define MAX_PTHREAD_NUM  256
void pthread_func(void *args)
{
        printf("create pthread task! pid: %d\n", syscall(__NR_gettid));
        while(1);
}
 
int main(int argc, char **argv)
{
        int i, n = 1;
        pthread_t tid[MAX_PTHREAD_NUM];
 
        if(argc == 2)
                n = atoi(argv[1]);
        for(i=0; i<n; i++)
                pthread_create(&tid[i], NULL, (void*)pthread_func, NULL);
 
        for(i=0; i<n; i++)
                pthread_join(tid[i], NULL);
 
        while(1);
        return 0;
}

 创建4个loop任务,观察top:

Top -H输出如下,可以看到有5个R状态任务,4个测试程序+top程序,总cpu使用率为400%。这4个测试程序分别瓜分了4个cpu核,每个任务实实在在的处于Running状态,每个程序占用100%cpu。

 再次启动4个loop任务,这次将4个任务绑定到一个cpu核上执行。看看结果:

 

Top输出如下,通过cpu绑核后,可以看到总cpu占用率只有100%,每个程序占用≈25%。但是我们看到这个4个任务还是处于R状态,但事实上,同一时刻只能存在一个真正运行的任务。所以说R状态有两种情况,一种是已经就绪了,随时可以运行,但尚未抢到cpu,另一种情况是真正的在cpu上执行。在操作系统眼里,这些任务都是R状态。所以,如top输出所示,CPU1核上,R状态任务有4个,但实际上真正运行只有1个。

 

进程状态之--→ S

S:缺少某种资源或等待事件完成,处于睡眠状态,等待被唤醒。进程也可以自己主动让住cpu,进入睡眠状态。该状态可以通过信号signal唤醒。

 

测试程序:

int main()
{
        printf("I'm a sleep task!\n");
        while(1) {
                sleep(100);
        }
}

睡眠任务运行:

 

Top输出:

 

可以看到长期处于睡眠态,由于是浅度睡眠可以接收信号,我们向进程发送信号kill -9 8337,可以看到进程8337被干掉,因为进程接收到sigkill信号后,被唤醒执行默认的信号处理程序,程序退出了。

 

 

进程状态之--→ D

D:disk sleep,是一种深度睡眠,通常是在等待io、磁盘。与S状态相比它不能通过信号唤醒。处于这种状态的进程必须拿到资源才能唤醒,否则一直睡觉,Kill -9也白扯。

 通常系统里不会长期出现D状态任务,如果长期出现D状态任务,多半是系统已经发生了问题。所以这个实验不好做,看来又要搞破坏了。

 

找一块嵌入式单板,在其linux系统中mount一个nfs文件系统,mount后将网线断开,此时df -h会卡死,长时间不能返回,事实上df进程已经处于D状态,正在等待io资源完成,io源于网络,此时网络已经断开!

 

我们telnet登陆到单板上看下情况,可以看到df -h确实处于D状态了!额外看到D后面有个+号,是什么鬼?代表前台进程,下篇文章会解释。

 

 此时的D状态任务像僵尸一般驻留在系统体内,不接收任何信号,ctrl+c、ctrl+z通通白废!

Kill -9 3380,kil -2 3380,向进程发信号也是如此。

 

我们再次接上网线,稍等片刻,系统输出nfs:server 192.168.1.168 ok字样,df -h重新复活了!串口又可以接收指令了。可见,D状态任务只有io资源满足的时候才能复活啊!

 

 

进程状态之--→ T

T:该状态表示进程处于停止状态。通常用于作业控制(job control),停止状态并不是说进程已经终止了,而是一个临时停止,举个例子:我们手机正在运行一个qq音乐进程,我们在房间内享受着优美的音乐,突然窗外传来一阵异样的噪音,这个时候我们赶紧把qq音乐暂停,爬在窗口看看是什么情况,原来是广场舞大妈们正在操练,好吧没什么大不了的,关上窗,继续播放qq音乐。当我们去看窗户的这段时间,qq进程就处理暂停状态,它并没有真的停止,也就是T状态。那么linux进程什么时候会进入T状态呢?有一种常见的方法让进程暂停执行,强制处于T状态,那就是CTRL+Z命令,该命令会让进程暂停执行。fg/bg命令让其恢复。稍后会用程序表明。另外,大家都知道gdb吧,有了T状态,我们就可以利用这个状态进行调试了,gdb在使用break设置断点的时候,实际上就是发一个stop信号给进程,让进程暂停,暂停后我们可以观察进程运行状态,变量值,等等信息,达到调试程序的功能。按下r键可以让程序继续运行,而这相当于给程序发了一个sigcont信号(继续运行)。单步调试也是同样的道理。

 

实验:

启动一个loop任务,让其暂停执行。

 

Top -p 8713看下任务状态,显示任务已经处于T状态。

Fg将任务再次唤醒,得到运行。

 

Tip:我们在编译yocto或者大型android类软件时,通常会耗时很久,这个时候我们想在编译的过程中,干点别的事,假如使用Ctrl+c来结束当前程序的执行。那么下次必须重新启动编译。但是如果是ctrl+z,只是先将任务暂停,爽完了,fg唤醒即可。

 

进程状态之--→ Z

Z:表示进程处于僵尸状态。什么?僵尸?系统里出现了这样的进程还能了得?事实上不用害怕,僵尸状态只是子进程退出时的一种临界状态,它并不可怕,处于僵尸态的进程所有占用资源已经释放(除了task_struct这个结构变量),留下task_struct结构,是为了让父进程知道子进程的死亡原因,来进行合适的处理。进程退出码保存在task_struct->exit_codel;父进程需要调用wait/waitpid来为子进程收尸,一旦wait系统调用后,子进程会从此消失。通常情况下系统中不会有Z状态进程,如果有,也不用怕,那就是父进程还没有来得及进行收尸。另外Z进程也是无法接收信号的。假如你看到系统里有个僵尸进程,你看着很不爽,你用kill -9来杀这个进程,是没有任何效果的,因为它已经死了,,只剩个躯体在那,你kill -9无非是在尸体上在多捅几刀,无任何意义。

 

创造一个僵尸程序:

void child_process()
{
        printf("I'm child process! pid=%d\n", getpid());
        sleep(1);
        _exit(0);
}
 
int main()
{
        int pid;
        int status;
 
        pid = fork();
 
        if(pid == 0) {
                child_process();
        } else {
                while(1);
                waitpid(pid, &status, 0);
        }
}

父进程创建一个子进程,子进程打印自己的pid,并退出,但父进程一直不去调用waitpid为子进程收尸,看看运行结果:

 

运行Top -p 9053,可以看到系统中有僵尸了!Zombie处于Z状态。

 

将父进程结束掉,子进程也随之退出。想想这是为什么?

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页