阅读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;
}
finder
和reader
都是默认设置
查看其源码发现似乎非常简单。。。似乎没有很特别的地方了。
//
// 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指令所用的头文件系统函数似乎都可以直接调用(这样想来还是很科学的,时空效率上就有了更高的保障)。