阅读top命令源码

课设要求实现一个系统监控器,本来打算在mac上实现一个类似activity monitor的程序,但似乎mac系统并没有这么开源。还是linux系统下的/proc文件系统方便。
看了一部分top命令的实现(看了两三个小时头都大了。。。),稍微记录一下,便于未来翻阅。

top.c

首先查看main函数前面的配置部分函数,主要是窗口初始化和对命令对选项解析。
之前以为应该只需要一个窗口,不过代码中初始化了四个窗口,以便于后面的扩展
(没有了解过top命令的用法,但是猜测这里应该是可以通过加参数扩展)

// Set up the raw/incomplete field group windows --
// they'll be finished off after startup completes.
// [ and very likely that will override most/all of our efforts ]
// [               --- life-is-NOT-fair ---                     ]
static void windows_stage1 (void)
{WIN_t *w;int i;for (i = 0; i < GROUPSMAX; i++) {w = &Winstk[i];w->winnum = i + 1;w->rc = Rc.win[i];w->captab[0] = Cap_norm;w->captab[1] = Cap_norm;w->captab[2] = w->cap_bold;w->captab[3] = w->capclr_sum;w->captab[4] = w->capclr_msg;w->captab[5] = w->capclr_pmt;w->captab[6] = w->capclr_hdr;w->captab[7] = w->capclr_rowhigh;w->captab[8] = w->capclr_rownorm;w->next = w + 1;w->prev = w - 1;++w;}/* fixup the circular chains... */Winstk[3].next = &Winstk[0];Winstk[0].prev = &Winstk[3];Curwin = Winstk;
}

看了注释小哥这说话风格有点不确认自己下的是不是官方源码了。。。

再来看下输出格式设计,用结构体的模式有利于未来扩展和复用。

// This structure consolidates the information that's used
// in a variety of display roles.
typedef struct FLD_t {const char    keys [4];      // order: New-on New-off Old-on Old-off// misaligned on 64-bit, but part of a table -- oh wellconst char   *head;          // name for col heads + toggle/reorder fieldsconst char   *fmts;          // sprintf format string for field displayconst int     width;         // field width, if applicableconst int     scale;         // scale_num type, if applicableconst QFP_t   sort;          // sort functionconst char   *desc;          // description for toggle/reorder fieldsconst int     lflg;          // PROC_FILLxxx flag(s) needed by this field
} FLD_t;

具体在top指令中的实现格式如下,数组组织形式:

static FLD_t Fieldstab[] = {
/* .lflg anomolies:P_UID, L_NONE  - natural outgrowth of 'stat()' in readproc        (euid)P_CPU, L_stat  - never filled by libproc, but requires times      (pcpu)P_CMD, L_stat  - may yet require L_CMDLINE in reframewins  (cmd/cmdline)L_EITHER       - must L_status, else 64-bit math, __udivdi3 on 32-bit !keys   head           fmts     width   scale  sort   desc                     lflg------  -----------    -------  ------  -----  -----  ----------------------   -------- */{ "AaAa", "   PID",      " %5u",     -1,    -1, SF(PID), "Process Id",           L_NONE   },{ "BbBb", "  PPID",      " %5u",     -1,    -1, SF(PPD), "Parent Process Pid",   L_EITHER },{ "CcQq", " RUSER   ",   " %-8.8s",  -1,    -1, SF(URR), "Real user name",       L_RUSER  },{ "DdCc", "  UID",       " %4u",     -1,    -1, SF(UID), "User Id",              L_NONE   },{ "EeDd", " USER    ",   " %-8.8s",  -1,    -1, SF(URE), "User Name",            L_EUSER  },{ "FfNn", " GROUP   ",   " %-8.8s",  -1,    -1, SF(GRP), "Group Name",           L_GROUP  },{ "GgGg", " TTY     ",   " %-8.8s",   8,    -1, SF(TTY), "Controlling Tty",      L_stat   },{ "HhHh", "  PR",        " %3d",     -1,    -1, SF(PRI), "Priority",             L_stat   },{ "IiIi", "  NI",        " %3d",     -1,    -1, SF(NCE), "Nice value",           L_stat   },{ "JjYy", " #C",         " %2u",     -1,    -1, SF(CPN), "Last used cpu (SMP)",  L_stat   },{ "KkEe", " %CPU",       " %#4.1f",  -1,    -1, SF(CPU), "CPU usage",            L_stat   },{ "LlWw", "   TIME",     " %6.6s",    6,    -1, SF(TME), "CPU Time",             L_stat   },{ "MmRr", "    TIME+ ",  " %9.9s",    9,    -1, SF(TME), "CPU Time, hundredths", L_stat   },{ "NnFf", " %MEM",       " %#4.1f",  -1,    -1, SF(RES), "Memory usage (RES)",   L_statm  },{ "OoMm", "  VIRT",      " %5.5s",    5, SK_Kb, SF(VRT), "Virtual Image (kb)",   L_statm  },{ "PpOo", " SWAP",       " %4.4s",    4, SK_Kb, SF(SWP), "Swapped size (kb)",    L_statm  },{ "QqTt", "  RES",       " %4.4s",    4, SK_Kb, SF(RES), "Resident size (kb)",   L_statm  },{ "RrKk", " CODE",       " %4.4s",    4, SK_Kb, SF(COD), "Code size (kb)",       L_statm  },{ "SsLl", " DATA",       " %4.4s",    4, SK_Kb, SF(DAT), "Data+Stack size (kb)", L_statm  },{ "TtPp", "  SHR",       " %4.4s",    4, SK_Kb, SF(SHR), "Shared Mem size (kb)", L_statm  },{ "UuJj", " nFLT",       " %4.4s",    4, SK_no, SF(FLT), "Page Fault count",     L_stat   },{ "VvSs", " nDRT",       " %4.4s",    4, SK_no, SF(DRT), "Dirty Pages count",    L_statm  },{ "WwVv", " S",          " %c",      -1,    -1, SF(STA), "Process Status",       L_EITHER },// next entry's special: '.head' will be formatted using table entry's own//                       '.fmts' plus runtime supplied conversion args!{ "XxXx", " COMMAND",    " %-*.*s",  -1,    -1, SF(CMD), "Command name/line",    L_EITHER },{ "YyUu", " WCHAN    ",  " %-9.9s",  -1,    -1, SF(WCH), "Sleeping in Function", L_stat   },// next entry's special: the 0's will be replaced with '.'!{ "ZzZz", " Flags   ",   " %08lx",   -1,    -1, SF(FLG), "Task Flags <sched.h>", L_stat   },
#if 0{ "..Qq", "   A",        " %4.4s",    4, SK_no, SF(PID), "Accessed Page count",  L_stat   },{ "..Nn", "  TRS",       " %4.4s",    4, SK_Kb, SF(PID), "Code in memory (kb)",  L_stat   },{ "..Rr", "  WP",        " %4.4s",    4, SK_no, SF(PID), "Unwritable Pages",     L_stat   },{ "Jj[{", " #C",         " %2u",     -1,    -1, SF(CPN), "Last used cpu (SMP)",  L_stat   },{ "..\\|"," Bad",        " %2u",     -1,    -1, SF(CPN), "-- must ignore | --",  0        },{ "..]}", " Bad",        " %2u",     -1,    -1, SF(CPN), "-- not used --",       0        },{ "..^~", " Bad",        " %2u",     -1,    -1, SF(CPN), "-- not used --",       0        },
#endif
};

main函数在完成配置和初始化后进入loop,其中有一个函数frame_make()

for (;;) {if (need_resize){need_resize = 0;wins_resize();}frame_make();

大致上是先获取了信息再放入窗口显示。那么这个summary_show()就是核心函数了。

static void frame_make (void)
{proc_t **ppt;int i, scrlins;// note: all libproc flags are managed by//       reframewins(), who also builds each window's column headersif (!Frames_libflags) {reframewins();memset(Pseudo_scrn, '\0', Pseudo_size);}Pseudo_row = Msg_row = scrlins = 0;ppt = summary_show();Max_lines = (Screen_rows - Msg_row) - 1;if (CHKw(Curwin, EQUWINS_cwo))wins_reflag(Flags_OFF, EQUWINS_cwo);// sure hope each window's columns header begins with a newline...putp(tg2(0, Msg_row));if (!Rc.mode_altscr) {// only 1 window to show so, piece o' cakeCurwin->winlines = Curwin->rc.maxtasks;window_show(ppt, Curwin, &scrlins);} else {// maybe NO window is visible but assume, pieces o' cakesfor (i = 0 ; i < GROUPSMAX; i++) {if (CHKw(&Winstk[i], VISIBLE_tsk)) {framehlp(i, Max_lines - scrlins);window_show(ppt, &Winstk[i], &scrlins);}if (Max_lines <= scrlins) break;}}// clear to end-of-screen (critical if last window is 'idleps off'),// then put the cursor in-its-place, and rid us of any prior frame's msg// (main loop must iterate such that we're always called before sleep)PUTT("%s%s%s%s",scrlins < Max_lines ? "\n"        : "",scrlins < Max_lines ? Cap_clr_eos : "",tg2(0, Msg_row),Cap_clr_eol);fflush(stdout);
}

当然window_show()也有一个要注意的地方,top命令不是显示所有进程,只显示部分的前提下需要显示对当前系统最重要的那部分,所以需要进行排序再输出。排序很简单,直接调用qsort即可,但应该按照哪种信息排序呢?

qsort(ppt, Frame_maxtask, sizeof(proc_t *), Fieldstab[q->rc.sortindx].sort);

这里的实现是可扩展的(可见应该可以通过调用时添加选项控制排序方式),可以看到在上面格式化控制的表格中也有排序信息,每个sortindx对应一种指标,默认配置存放在系统配置文件中,在初始化时读入。

#define SYS_RCFILESPEC  "/etc/toprc"// read part of an old-style config in /etc/toprc
fd = open(SYS_RCFILESPEC, O_RDONLY);

summary_show()函数,逐个重点来看

      // sleep for half a secondtv.tv_sec = 0;tv.tv_usec = 500000;select(0, NULL, NULL, NULL, &tv);  // ought to loop until done

select函数这个我乍一眼没想起来是哪用过,看注释明白功能后想起来Socket编程里面用过这个函数。

//等待用户连接请求或用户数据到来或会话socke可发送数据
if((this->numOfSocketSignaled = select(0,&this->rfds,&this->wfds,NULL,NULL)) == SOCKET_ERROR){ //select函数返回有可读或可写的socket的总数,保存在rtn里.最后一个参数设定等待时间,如为NULL则为阻塞模式cout << "select() failed with error!\n";return -1;
}

诶我这个菜鸡。。。脑子里只想到用sleep函数去实现。。。
然后是核心的进程函数更新

 p_table = procs_refresh(p_table, Frames_libflags);

而在更新进程的众多调用函数中核心的又是

if (unlikczely(!(ptsk = readproc(PT, table[curmax])))) break;

如此跳转到

readproc.c

//
/* readproc: return a pointer to a proc_t filled with requested info about the
* next process available matching the restriction set.  If no more such
* processes are available, return a null pointer (boolean false).  Use the
* passed buffer instead of allocating space if it is non-NULL.  *//* This is optimized so that if a PID list is given, only those files are
* searched for in /proc.  If other lists are given in addition to the PID list,
* the same logic can follow through as for the no-PID list case.  This is
* fairly complex, but it does try to not to do any unnecessary work.
*/
proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p) {proc_t *ret;proc_t *saved_p;PT->did_fake=0;
//  if (PT->taskdir) {
//    closedir(PT->taskdir);
//    PT->taskdir = NULL;
//    PT->taskdir_user = -1;
//  }saved_p = p;if(!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */for(;;){// fills in the path, plus p->tid and p->tgidif (unlikely(! PT->finder(PT,p) )) goto out;// go read the process dataret = PT->reader(PT,p);if(ret) return ret;}out:if(!saved_p) free(p);// FIXME: maybe set tid to -1 here, for "-" in display?return NULL;
}

finderreader都是默认设置
查看其源码发现似乎非常简单。。。似乎没有很特别的地方了。

//
// This finds tasks in /proc/*/task/ in the traditional way.
// Return non-zero on success.
static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) {static struct direct *ent;		/* dirent handle */if(PT->taskdir_user != p->tgid){if(PT->taskdir){closedir(PT->taskdir);}// use "path" as some tmp spacesnprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid);PT->taskdir = opendir(path);if(!PT->taskdir) return 0;PT->taskdir_user = p->tgid;}for (;;) {ent = readdir(PT->taskdir);if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;}t->tid = strtoul(ent->d_name, NULL, 10);t->tgid = p->tgid;t->ppid = p->ppid;  // cover for kernel behavior? we want both actually...?snprintf(path, PROCPATHLEN, "/proc/%d/task/%s", p->tgid, ent->d_name);return 1;
}
//
// This reads /proc/*/task/* data, for one task.
// p is the POSIX process (task group summary) (not needed by THIS implementation)
// t is the POSIX thread (task group member, generally not the leader)
// path is a path to the task, with some room to spare.
static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) {static struct stat sb;		// stat() bufferstatic char sbuf[1024];	// buffer for stat,statmunsigned flags = PT->flags;//printf("hhh\n");if (unlikely(stat(path, &sb) == -1))	/* no such dirent (anymore) */goto next_task;//    if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
//	goto next_task;			/* not one of the requested uids */t->euid = sb.st_uid;			/* need a way to get real uid */t->egid = sb.st_gid;			/* need a way to get real gid *///printf("iii\n");if (flags & PROC_FILLSTAT) {         /* read, parse /proc/#/stat */if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 ))goto next_task;			/* error reading /proc/#/stat */stat2proc(sbuf, t);				/* parse /proc/#/stat */}if (unlikely(flags & PROC_FILLMEM)) {	/* read, parse /proc/#/statm */
#if 0if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 ))statm2proc(sbuf, t);		/* ignore statm errors here */
#elset->size     = p->size;t->resident = p->resident;t->share    = p->share;t->trs      = p->trs;t->lrs      = p->lrs;t->drs      = p->drs;t->dt       = p->dt;
#endif}						/* statm fields just zero */if (flags & PROC_FILLSTATUS) {         /* read, parse /proc/#/status */if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){status2proc(sbuf, t, 0);}}/* some number->text resolving which is time consuming */if (flags & PROC_FILLUSR){memcpy(t->euser,   user_from_uid(t->euid), sizeof t->euser);if(flags & PROC_FILLSTATUS) {memcpy(t->ruser,   user_from_uid(t->ruid), sizeof t->ruser);memcpy(t->suser,   user_from_uid(t->suid), sizeof t->suser);memcpy(t->fuser,   user_from_uid(t->fuid), sizeof t->fuser);}}/* some number->text resolving which is time consuming */if (flags & PROC_FILLGRP){memcpy(t->egroup, group_from_gid(t->egid), sizeof t->egroup);if(flags & PROC_FILLSTATUS) {memcpy(t->rgroup, group_from_gid(t->rgid), sizeof t->rgroup);memcpy(t->sgroup, group_from_gid(t->sgid), sizeof t->sgroup);memcpy(t->fgroup, group_from_gid(t->fgid), sizeof t->fgroup);}}#if 0if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG))	/* read+parse /proc/#/cmdline */t->cmdline = file2strvec(path, "cmdline");elset->cmdline = NULL;if (unlikely(flags & PROC_FILLENV))			/* read+parse /proc/#/environ */t->environ = file2strvec(path, "environ");elset->environ = NULL;
#elset->cmdline = p->cmdline;  // better not free these until done with all threads!t->environ = p->environ;
#endift->ppid = p->ppid;  // ought to put the per-task ppid somewherereturn t;
next_task:return NULL;
}

后面便是系统信息的函数了

sysinfo.c

// Display Uptime and Loadavgif (CHKw(Curwin, View_LOADAV)) {if (!Rc.mode_altscr) {show_special(0, fmtmk(LOADAV_line, Myname, sprint_uptime()));} else {show_special(0,fmtmk(CHKw(Curwin, VISIBLE_tsk) ? LOADAV_line_alt : LOADAV_line,Curwin->grpname,sprint_uptime()));}Msg_row += 1;}// Display Task and Cpu(s) Statesif (CHKw(Curwin, View_STATES)) {show_special(0,fmtmk(STATES_line1,Frame_maxtask, Frame_running, Frame_sleepin, Frame_stopped, Frame_zombied));Msg_row += 1;smpcpu = cpus_refresh(smpcpu);if (CHKw(Curwin, View_CPUSUM)) {// display just the 1st /proc/stat linesummaryhlp(&smpcpu[Cpu_tot], "Cpu(s):");} else {int i;char tmp[SMLBUFSIZ];// display each cpu's states separatelyfor (i = 0; i < Cpu_tot; i++) {snprintf(tmp, sizeof(tmp), "Cpu%-3d:", smpcpu[i].id);summaryhlp(&smpcpu[i], tmp);}}}// Display Memory and Swap statsmeminfo();if (CHKw(Curwin, View_MEMORY)) {show_special(0, fmtmk(MEMORY_line1, kb_main_total, kb_main_used, kb_main_free, kb_main_buffers));show_special(0, fmtmk(MEMORY_line2, kb_swap_total, kb_swap_used, kb_swap_free, kb_main_cached));Msg_row += 2;}

大概看了一遍功能函数,解析字符串–>在表中对应位置转化数据存入–>计算
还是没啥复杂的,不过这个写法值得借鉴一哈,FILE_TO_BUF,我在课设中写的是直接对文件流进行操作的,而不是将文件流信息全部读入buf(而且是公有的)再去解析。

  const int mem_table_count = sizeof(mem_table)/sizeof(mem_table_struct);FILE_TO_BUF(MEMINFO_FILE,meminfo_fd);kb_inactive = ~0UL;head = buf;for(;;){tail = strchr(head, ':');if(!tail) break;*tail = '\0';//超过缓存区大小(应该是一种特殊情况,和任务名的大小无关)if(strlen(head) >= sizeof(namebuf)){head = tail+1;goto nextline;}strcpy(namebuf,head);//二分法检索出对应名字found = bsearch(&findme, mem_table, mem_table_count,sizeof(mem_table_struct), compare_mem_table_structs);head = tail+1;if(!found) goto nextline;//将:后的数据转为长整型输出,同时将tail更新(0)*(found->slot) = strtoul(head,&tail,10);
nextline:tail = strchr(head, '\n');if(!tail) break;head = tail+1;}if(!kb_low_total){  /* low==main except with large-memory support */kb_low_total = kb_main_total;kb_low_free  = kb_main_free;}if(kb_inactive==~0UL){kb_inactive = kb_inact_dirty + kb_inact_clean + kb_inact_laundry;}kb_swap_used = kb_swap_total - kb_swap_free;kb_main_used = kb_main_total - kb_main_free;
}

并没有全看完这些源码。。。整体框架大概看了一下,写法是不错,用c写工具–还是得有很多底层知识才知道怎样减少开销和维护的。同时这些代码的架构真的很厉害,值得学习。


= =还是太naive了,发现可以直接用readproc函数获取进程所有信息,以为课设不能这么干呢,原来是要求这么干的。这样top指令所用的头文件系统函数似乎都可以直接调用(这样想来还是很科学的,时空效率上就有了更高的保障)。