6 procs_refresh()
procs_refresh()得到本轮采样中的进程数据。
-
调用procs_hlp()得到本轮采样中系统全局的clock tick(时钟滴答)。
-
调用openproc()初始化PROCTAB结构。其中,
- 两个成员finder和reader()是函数指针。前者用于查找下一个进程的pid,后者指定进程pid得到进程数据。
- 成员pids指定一组进程pid。通过命令选项 -p <pid1,pid2,pid3>可以指定这组值。
typedef struct PROCTAB
{
DIR* procfs;
int(*finder)(struct PROCTAB *, proc_t *);
proc_t*(*reader)(struct PROCTAB *, proc_t *);
pid_t* pids;
char path[PROCPATHLEN];
unsigned pathlen;
...
} PROCTAB;
- 在while()循环中,得到本轮采样中所有进程数据(包括clock_tick),保存在本地数组private_ppt[]中。其中,
static proc_t** private_ppt;
-
调用alloc_r(),为当前进程分配proc_t实例,以便保存进程数据。
-
调用read_something()得到进程数据(包括clock_tick)。read_something()是函数指针。如果通过命令项-H指定了线程模式,则指向readeither(),否则指向readproc()。这里将分析后者。
-
调用procs_hlp()进一步加工进程数据。比如,从累积量计算差值。
-
调用memcpy()将进程数据从private_ppt复制到全局数组Winstk[]中。Winstk是为多个子窗口准备的,每个实例对应一个子窗口。top最多支持4个子窗口,这里只分析1个的情况。
#define GROUPSMAX 4
static WIN_t Winstk [GROUPSMAX];
7 procs_hlp()
7.1 “单cpu核” vs “多cpu核”
如procs_refresh()中所说,procs_hlp()的工作有两部分。这里先说计算全局clock tik数据的情况。
全局数据计算有两种计算方法:相对于单个cpu核,或者相对于所有cpu核。全局变量Rc的成员mode_irixps保存了这个设置。默认值是相对于单个cpu核。
typedef struct RCF_t {
char id; // rcfile version id
int mode_altscr; // 'A' - Alt display mode (multi task windows)
int mode_irixps; // 'I' - Irix vs. Solaris mode (SMP-only)
float delay_time; // 'd'/'s' - How long to sleep twixt updates
int win_index; // Curwin, as index
RCW_t win [GROUPSMAX]; // a 'WIN_t.rc' for each window
int fixed_widest; // 'X' - wider non-scalable col addition
int summ_mscale; // 'E' - scaling of summary memory values
int task_mscale; // 'e' - scaling of process memory values
int zero_suppress; // '0' - suppress scaled zeros toggle
} RCF_t;
static RCF_t Rc;
全局clock tick值是自从系统启动以来的clock tick,这实际上也是单个cpu核的值。有几个cpu核,所有cpu核的clock tick就是这个值的几倍。
由于多线程程序使用多个核,它可能不止一个使用cpu核,那么按照单cpu核的计算方式,它的cpu占用率可能超过100%。
7.2 计算全局数据
procs_hlp()计算全局数据的步骤如下。
- 调用uptime()。它得到自开机以来clock tik,这是个累计量。解析的结果保存在本地变量uptime_cur中。
- 这个值从/proc/uptime得到的。第一个值是clock tick,第二个是其中idle状态下的clock tick。
$ cat /proc/uptime
19217.09 149948.29
- 用本地变量et保存uptime_cur与上个累计量uptime_save的差值。 同时将当前clock tick保存在uptime_sav中,以备下一轮使用。
- 计算全局变量Frame_etscale,这个值后面计算进程CPU占用率时会用到。
static float Frame_etscale;
Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : smp_num_cpus));
这里的分析,计算方式是单cpu核, Rc.mode_irixps = 1。所以公式可以简化为:
Frame_etscale = 100.0f / ((float)Hertz * (float)et);
其中Hertz变量的值是在init_libproc()中计算的。
8 init_libproc()
由于init_libproc()用attribute((constructor))修饰,它将在main()之前被调用。
static void init_libproc(void) __attribute__((constructor));
- init_libproc()调用old_Hertz_hack()。
- 后者调用sysconf(_SC_CLK_TCK),得到Hertz值。Hertz表示机器上每秒有多少个clock tick。
- 如下的脚本也可以得到当前机器上的Hertz值。
$ cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
CONFIG_HZ=250
9 readproc()
readproc()的步骤如下:
- 调用PROCTAB.finder()函数。如前所说,实际上是simple_nextpid()。simple_nextpid()遍历/proc下的进程目录,得到下一个进程id.
- 调用PROCTAB.reader()。如前所说,实际上是simple_readproc()。它读取本轮采样中,进程的运行数据。
- 调用file2str(),从/proc/<pid>/stat中读取内容。这里记录进程的运行数据,其中第14、15个字段(这里是201018和11165), 是开机以来该进程的用户态clock tick和内核态clock tick。两者相加就是进程的clock tick,这个是累计量。
$ cat /proc/2211/stat
2211 (compiz) S 1734 2211 2211 0 -1 4194304 1173835 5807 254 1 201018 11165 733 345 20 0 20 0 4109 1569832960 69715 18446744073709551615 4194304 4204954 140730964998528 0 0 0 0 4096 81923 0 0 0 17 6 0 0 83 0 0 6303088 6304056 7000064 140730965002255 140730965002262 140730965002262 140730965004264 0
- 调用stat2proc()解析结果,将这两个值保存到proc_t结构的成员utime和stime中。
typedef struct proc_t
{
int tid,
int ppid;
unsigned pcpu; // %CPU usage
utime, // user-mode CPU time accumulated by process
stime, // kernel-mode CPU time accumulated by process
...
} proc_t;
- 调用file2str(),得到其他进程运行数据,如内存数据/proc/<pid>/statm、运行状态数据/proc/<pid>/status等。
10 再说procs_hlp()
procs_hlp()工作的第二部分,是计算本轮采样中进程的clock tick差值。
- 本地变量tics保存用户态和核心态的clock tick之和。
typedef unsigned long long TIC_t;
TIC_t tics = (this->utime + this->stime)
上轮采样的clock tick保存在全局Hash表PHist_new结构的成员tics中。这个Hash表的key值是进程pid。
typedef struct HST_t {
TIC_t tics; // last frame's tics count
unsigned long maj, min; // last frame's maj/min_flt counts
int pid; // record 'key'
int lnk; // next on hash chain
} HST_t;
static HST_t *PHist_new;
在proc_t的成员pcpu中,保存本轮采样的clock tick的差值。
11 window_show()
window_show()的步骤如下。
- 调用PUFF()输出进程数据表的表头
- 缺省情况下按父子关系组织进程列表,调用forest_create()显示进程列表。
- 但这里的分析指定选项 -o %CPU,将按”%CPU”列排序,也就是按CPU占用率排序。调用qsort()排序。
- 遍历Winstk[]中的所有进程,调用task_show()显示。
12 task_show()
task_show显示本轮采样中该进程的所有数据。
它遍历进程数据的所有列,计算每列的值。
在遍历%CPU列时,
- 计算cpu占用率。
float u = (float)p->pcpu * Frame_etscale;
用前面Frame_etscale的计算式替代一下,可以得到如下计算式。为了看得更清楚,这里删掉了一些转型操作。
float u = p->pcpu / (Hertz * (uptime_cur - uptime_sav)) * 100.0f;
在这个计算式中,
-
uptime_cur - uptime_sav是这一轮显示中,系统(或单个cpu)逝去了多少秒,
-
Hertz是在本系统的设置中,每秒有多少个clock tick。
-
所以Hertz * (uptime_cur - uptime_sav)是系统(或单个cpu)逝去了多少个clock tick。
-
p->pcpu是这一轮显示中,该进程占用了多少个clock tick.
-
所以p->pcpu / (Hertz * (uptime_cur - uptime_sav))就是进程的clock tick在系统(单个cpu)的clock tick所占的比例,也就是CPU占用率。
-
最后这个值乘以100.0,只是显示百分比值。
-
调用scale_pcnt()显示这个百分比值。缺省情况下,snprintf()的format指定为%#.1f,也就是小数点后显示1位数字。这样的话,如果这个值低于0.1%,就会显示为0.0%。
网友评论