procps-ng 3.3.10 源代码分析 - top (二)

6 procs_refresh()

procs_refresh()得到本轮采样中的进程数据。

  • 调用procs_hlp()得到本轮采样中系统全局的clock tick(时钟滴答)。

  • 调用openproc()初始化PROCTAB结构。其中,

    • 两个成员finder和reader()是函数指针。前者用于查找下一个进程的pid,后者指定进程pid得到进程数据。
    • 成员pids指定一组进程pid。通过命令选项 -p 可以指定这组值。
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//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//statm、运行状态数据/proc//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%。

你可能感兴趣的:(procps-ng 3.3.10 源代码分析 - top (二))