top命令原理解析

1、研究目的

根据各个方向对于应用监控系统资源的使用情况。

2、关于proc

2.1、关于/proc/[pid]/status

经过研究,top命令也是主要使用了proc的接口进行读取各个进程的信息,所以对proc虚拟文件系统进行了研究。

我们重点关注/proc/[pid]/status中为我们提供的信息。这些信息可以对进程的状态进行监控。

主要信息有:

Name:该进程的名称

State:当前进程的状态,包括R(running),S(sleeping),D(disk sleep),T(stopped),Z (zombie),X (dead)

Tgid:进程组ID

Pid:进程IO

Ppid:父进程的PID

TracerPid:追踪该进程的PID

Uid,Gid: 进程所有者的用户id

FDSize:当前分配的文件描述符

Groups:补充的组列表

VmPeak:最高虚拟内存占用大小

VmSize:虚拟内存大小

VmLck:锁住的内存大小(see mlock(3))

VmPin:压住的内存,这些页不能移动,因为一些东西需要直接使用物理内存

VmHWM:最大的RSS

VmRSS:虚拟内存驻留集合大小。这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。

VmData, VmStk, VmExe:数据、堆、文本段大小

VmLib:共享库的大小

VmPTE:入口页表大小

VmPMD:二级页表大小

VmSwap:共享内存的大小

Threads:线程数量

SigQ:该属性有两个数据,第一个数据指当前的消息队列,第二个是消息队列的资源限制。

SigPnd,ShdPnd:为线程和进程固定的signals。

SigBlk, SigIgn, SigCgt:关于堵塞、忽略、捕捉的信号掩码。

CapInh, CapPrm, CapEff: 性能使能掩码

CapBnd:性能绑定设置

CapAmb:环境性能设置

Seccomp:Seccomp模式

Cpus_allowed:运行该进程的CPU上的掩码

Cpus_allowed_list:

Mems_allowed:被允许的内存节点的掩码

Mems_allowed_list:

voluntary_ctxt_switches, nonvoluntary_ctxt_switches:自发和非自发的上下文切换次数。

2.2、关于/proc/[pid]/stat

[root@localhost ~]# cat /proc/6873/stat
6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0

每个参数意思为:
参数解释

pid=6873 进程(包括轻量级进程,即线程)号
comm=a.out 应用程序或命令的名字
task_state=R 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead
ppid=6723 父进程ID
pgid=6873 线程组号
sid=6723 c该任务所在的会话组ID
tty_nr=34819(pts/3) 该任务的tty终端的设备号,INT(34817/256=主设备号,(34817-主设备号)=次设备号
tty_pgrp=6873 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。
task->flags=8388608 进程标志位,查看该任务的特性
min_flt=77 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数
cmin_flt=0 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目
maj_flt=0 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数
cmaj_flt=0 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目
utime=1587 该任务在用户态运行的时间,单位为jiffies
stime=1 该任务在核心态运行的时间,单位为jiffies
cutime=0 累计的该任务的所有的waited-for进程曾经在用户态运行的时间,单位为jiffies
cstime=0 累计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies
priority=25 任务的动态优先级
nice=0 任务的静态优先级
num_threads=3 该任务所在的线程组里线程的个数
it_real_value=0 由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位.
start_time=5882654 该任务启动的时间,单位为jiffies
vsize=1409024(page) 该任务的虚拟地址空间大小
rss=56(page) 该任务当前驻留物理地址空间的大小
Number of pages the process has in real memory,minu 3 for administrative purpose.
这些页可能用于代码,数据和栈。
rlim=4294967295(bytes) 该任务能驻留物理地址空间的最大值
start_code=134512640 该任务在虚拟地址空间的代码段的起始地址
end_code=134513720 该任务在虚拟地址空间的代码段的结束地址
start_stack=3215579040 该任务在虚拟地址空间的栈的结束地址
kstkesp=0 esp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致.
kstkeip=2097798 指向将要执行的指令的指针, EIP(32 位指令指针)的当前值.
pendingsig=0 待处理信号的位图,记录发送给进程的普通信号
block_sig=0 阻塞信号的位图
sigign=0 忽略的信号的位图
sigcatch=082985 被俘获的信号的位图
wchan=0 如果该进程是睡眠状态,该值给出调度的调用点
nswap 被swapped的页数,当前没用
cnswap 所有子进程被swapped的页数的和,当前没用
exit_signal=17 该进程结束时,向父进程所发送的信号
task_cpu(task)=0 运行在哪个CPU上
task_rt_priority=0 实时进程的相对优先级别
task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程  

3、top命令关于cpu占用率的实现

static void get_jiffy_counts(void)
{		
    ···
    while (1) {
                cpu_jif = xrealloc_vector(cpu_jif, 1, num_cpus);
                if (read_cpu_jiffy(fp, &cpu_jif[num_cpus]) <= 4)
                    break;
                num_cpus++;
            }
    ···
}

根据以上代码可知,获取cpu_jiffy时,是需要获取多个cpu的jiffy的。以下是获取jiffy的代码。

static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif)
{
#if !ENABLE_FEATURE_TOP_SMP_CPU
	static const char fmt[] = "cpu %llu %llu %llu %llu %llu %llu %llu %llu";
#else
	static const char fmt[] = "cp%*s %llu %llu %llu %llu %llu %llu %llu %llu";
#endif
	int ret;

	if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */)
		return 0;
	ret = sscanf(line_buf, fmt,
			&p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle,
			&p_jif->iowait, &p_jif->irq, &p_jif->softirq,
			&p_jif->steal);
	if (ret >= 4) {
		p_jif->total = p_jif->usr + p_jif->nic + p_jif->sys + p_jif->idle
			+ p_jif->iowait + p_jif->irq + p_jif->softirq + p_jif->steal;
		/* procps 2.x does not count iowait as busy time */
		p_jif->busy = p_jif->total - p_jif->idle - p_jif->iowait;
	}
	return ret;
}

关于显示各个进程的CPU使用情况:

	while (--lines_rem >= 0) {
		unsigned col;
		CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift);
#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE
		CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift);
#endif

		if (s->vsz >= 100000)
			sprintf(vsz_str_buf, "%6ldm", s->vsz/1024);
		else
			sprintf(vsz_str_buf, "%7ld", s->vsz);
		/* PID PPID USER STAT VSZ %MEM [%CPU] COMMAND */
		col = snprintf(line_buf, scr_width,
				"\n" "%5u%6u %-8.8s %s%s" FMT
				IF_FEATURE_TOP_SMP_PROCESS(" %3d")
				IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(FMT)
				" ",
				s->pid, s->ppid, get_cached_username(s->uid),
				s->state, vsz_str_buf,
				SHOW_STAT(pmem)
				IF_FEATURE_TOP_SMP_PROCESS(, s->last_seen_on_cpu)
				IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(, SHOW_STAT(pcpu))
		);
		if ((int)(col + 1) < scr_width)
			read_cmdline(line_buf + col, scr_width - col, s->pid, s->comm);
		fputs(line_buf, stdout);
        /* s->pcpu当前进程的CPU时间片;
           total_pcpu全部时间片;
           当前busy时间片-之前busy时间片;
           当前全部时间片-之前全部时间片
        */
		 printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu,
			cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); 
		s++;
	}

4、busybox_top与procps-ng_top的不同

两者不同之处较多,其中比较明显的不同之处就是关于CPU占用的处理的不同。

root@am57xx-evm:/# stress -c 2 --verbose --timeout 1m
stress: info: [1224] dispatching hogs: 2 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1224] using backoff sleep of 6000us
stress: dbug: [1224] setting timeout to 60s
stress: dbug: [1224] --> hogcpu worker 2 [1225] forked
stress: dbug: [1224] using backoff sleep of 3000us
stress: dbug: [1224] setting timeout to 60s
stress: dbug: [1224] --> hogcpu worker 1 [1226] forked
stress: dbug: [1224] <-- worker 1225 signalled normally
stress: dbug: [1224] <-- worker 1226 signalled normally
stress: info: [1224] successful run completed in 60s

调用stress命令,为两个CPU分别添加了一条进程。可以进行两个CPU的压力测试。

下图就是busybox——top的执行情况:

可以看到两条CPU压力测试线程+app_host应用约为100%,并且每个进程都有标记在具体哪个CPU上运行,但是计算的数值并不是很准确,例如1126、866进程运行在CPU0上,而相加的占用率却超过了50%,后续可针对该问题进一步分析。

在这里插入图片描述

另外一种CPU占用率计算方式是针对不同的CPU分别计算CPU占用率,算出的CPU占用率是可以达到200%的。

在这里插入图片描述

两种计算方式都能在一定程度上反应CPU的使用程度,一般第一种实现较为简单,对查看CPU占用率精度要求不高的话,可以采用第一种计算方式。

5、自写cpu检测测试

在这里插入图片描述

使用压力测试,可以看到cpu占用率与busybox测出的cpu占用率基本相同。

6、关于代码分析

6.1、关于总CPU时间获取

首先分析获取总的CPU占用时间的函数:

  unsigned int get_cpu_total_occupy()
  {
      FILE *fd;         //定义文件指针fd
      char buff[1024] = {0};  //定义局部变量buff数组为char类型大小为1024
      total_cpu_occupy_t t;

      fd = fopen ("/proc/stat", "r"); //以R读的方式打开stat文件再赋给指针fd
      fgets (buff, sizeof(buff), fd); //从fd文件中读取长度为buff的字符串再存到起始地址为buff这个空间里
     /*下面是将buff的字符串根据参数format后转换为数据的结果存入相应的结构体参数 */
     char name[16];//暂时用来存放字符串
     sscanf (buff, "%s %u %u %u %u", name, &t.user, &t.nice,&t.system, &t.idle);


     fprintf (stderr, "====%s:%u %u %u %u====\n", name, t.user, t.nice,t.system, t.idle);
     fclose(fd);     //关闭文件fd
     return (t.user + t.nice + t.system + t.idle);
 }

以上函数中用到了一个结构体:

 typedef struct        //声明一个occupy的结构体
 {
         unsigned int user;  //从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。
         unsigned int nice;  //从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
         unsigned int system;//从系统启动开始累计到当前时刻,处于核心态的运行时间
         unsigned int idle;  //从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间iowait (12256) 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41)
 }total_cpu_occupy_t;

通过/proc/stat收到的数据传入到buff当中,然后再借助sscanf将buff的数据按照占位符分配给各个变量。

root@am57xx-evm:/proc# cat stat
cpu 469 0 1014 8895 67 0 4 0 0 0
cpu0 273 0 569 4360 22 0 3 0 0 0
cpu1 196 0 445 4535 45 0 1 0 0 0
intr 23123 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4955 0 0 0 0 0 7 0 1 0 57 0 0 506 0 8 0 0 0 0 0 0 0 0
ctxt 40838
btime 1601481907
processes 892
procs_running 1
procs_blocked 0
softirq 18111 4 3441 10 328 0 0 707 2292 0 11329

user (469) 从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。

nice (0) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间

system (1014) 从系统启动开始累计到当前时刻,处于核心态的运行时间

idle (8895) 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间

iowait (67) 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41)

irq (0) 从系统启动开始累计到当前时刻,硬中断时间(since 2.6.0-test4)

softirq (4) 从系统启动开始累计到当前时刻,软中断时间(since 2.6.0-test4)

steal (0) 其他虚拟环境下操作系统占用的时间

guest (0) 在Linux控制下的虚拟CPU的操作系统所占用的时间

guest_nice (0)虚拟CPU的操作系统nice占用的时间

总的CPU时间等于以上信息全部加起来,通过这些可以了解到之前的计算方式还是不够准确,应该将这些所有的CPU占用时间全部加起来,才能更准确的计算当前CPU运行的占用率。

6.2、获取进程的CPU时间

通过获取/proc/[pid]/stat的信息来获取关于进程的CPU占用率信息。

  unsigned int get_cpu_process_occupy(const pid_t p)
  {
      char file[64] = {0};//文件名
      process_cpu_occupy_t t;
      FILE *fd;         //定义文件指针fd
      char line_buff[1024] = {0};  //读取行的缓冲区
      sprintf(file,"/proc/%d/stat",p);//文件中第11行包含着

      fprintf (stderr, "current pid:%d\n", p);
      fd = fopen (file, "r"); //以R读的方式打开文件再赋给指针fd
      fgets (line_buff, sizeof(line_buff), fd); //从fd文件中读取长度为buff的字符串再存到起始地址为buff这个空间里
      sscanf(line_buff,"%u",&t.pid);//取得第一项
      char* q = get_items(line_buff,PROCESS_ITEM);//取得从第14项开始的起始指针
      sscanf(q,"%u %u %u %u",&t.utime,&t.stime,&t.cutime,&t.cstime);//格式化第14,15,16,17项

      fprintf (stderr, "====pid%u:%u %u %u %u====\n", t.pid, t.utime,t.stime,t.cutime,t.cstime);
      fclose(fd);     //关闭文件fd
      return (t.utime + t.stime + t.cutime + t.cstime);
  }

在之前已经对/proc/[pid]/stat已经进行了分析,这里不再赘述。

6.3、通过差值计算进程CPU占用率

之前获取的总CPU时间和进程CPU时间实在不断变化的,通过一段时间内的差值,就可以计算出在一段时间内一个进程所占用的CPU时间比例,这个比例就叫CPU占用率。

 float get_pcpu(pid_t p)
 {
     unsigned int totalcputime1,totalcputime2;
       unsigned int procputime1,procputime2;
     totalcputime1 = get_cpu_total_occupy();//获取当前总的CPU运行时间
     procputime1 = get_cpu_process_occupy(p);//获取当前进程的CPU使用时间
     usleep(1000000);//延迟1000毫秒
     totalcputime2 = get_cpu_total_occupy();//再次获取总的CPU运行时间
     procputime2 = get_cpu_process_occupy(p);//再次获取进程的CPU使用时间
     float pcpu = 100.0*(procputime2 - procputime1)/(totalcputime2 - totalcputime1);//计算CPU使用率
     fprintf(stderr,"====pcpu:%.6f\n====",pcpu);
     return pcpu;
 }

7、关于进程占用内存的监控

7.1、关于内存使用情况的获取

以下分别是总内存的获取和进程使用的内存的获取,通过这两个函数,可以最终计算出一个进程的内存使用率。

  int get_total_mem()
  {
      char* file = "/proc/meminfo";//文件名

      FILE *fd;         //定义文件指针fd
      char line_buff[256] = {0};  //读取行的缓冲区                                                                                                
      fd = fopen (file, "r"); //以R读的方式打开文件再赋给指针fd

      //获取memtotal:总内存占用大小
      int i;
      char name[32];//存放项目名称
      int memtotal;//存放内存峰值大小
      fgets (line_buff, sizeof(line_buff), fd);//读取memtotal这一行的数据,memtotal在第1行
      sscanf (line_buff, "%s %d", name,&memtotal);
      fprintf (stderr, "====%s:%d====\n", name,memtotal);
      fclose(fd);     //关闭文件fd
      return memtotal;
  }


int get_phy_mem(const pid_t p)
{
      char file[64] = {0};//文件名

      FILE *fd;         //定义文件指针fd
      char line_buff[256] = {0};  //读取行的缓冲区
      sprintf(file,"/proc/%d/status",p);//文件中第11行包含着
 
      fprintf (stderr, "current pid:%d\n", p);                                                                                                   
      fd = fopen (file, "r"); //以R读的方式打开文件再赋给指针fd

      //获取vmrss:实际物理内存占用
      int i;
      char name[32];//存放项目名称
      int vmrss;//存放内存峰值大小
      for (i=0;i<VMRSS_LINE-1;i++)
      {   
          fgets (line_buff, sizeof(line_buff), fd);
      }//读到第15行
      fgets (line_buff, sizeof(line_buff), fd);//读取VmRSS这一行的数据,VmRSS在第15行
      sscanf (line_buff, "%s %d", name,&vmrss);
      fprintf (stderr, "====%s:%d====\n", name,vmrss);
      fclose(fd);     //关闭文件fd
      return vmrss;
  }


  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值