top命令源码分析
閱讀top命令源碼
課設要求實現(xiàn)一個系統(tǒng)監(jiān)控器,本來打算在mac上實現(xiàn)一個類似activity monitor的程序,但似乎mac系統(tǒng)并沒有這么開源。還是linux系統(tǒng)下的/proc文件系統(tǒng)方便。
看了一部分top命令的實現(xiàn)(看了兩三個小時頭都大了。。。),稍微記錄一下,便于未來翻閱。
top.c
首先查看main函數(shù)前面的配置部分函數(shù),主要是窗口初始化和對命令對選項解析。
之前以為應該只需要一個窗口,不過代碼中初始化了四個窗口,以便于后面的擴展
(沒有了解過top命令的用法,但是猜測這里應該是可以通過加參數(shù)擴展)
// 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指令中的實現(xiàn)格式如下,數(shù)組組織形式:
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函數(shù)在完成配置和初始化后進入loop,其中有一個函數(shù)frame_make()
for (;;) {if (need_resize){need_resize = 0;wins_resize();}frame_make();
大致上是先獲取了信息再放入窗口顯示。那么這個summary_show()就是核心函數(shù)了。
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命令不是顯示所有進程,只顯示部分的前提下需要顯示對當前系統(tǒng)最重要的那部分,所以需要進行排序再輸出。排序很簡單,直接調(diào)用qsort即可,但應該按照哪種信息排序呢?
qsort(ppt, Frame_maxtask, sizeof(proc_t *), Fieldstab[q->rc.sortindx].sort);
這里的實現(xiàn)是可擴展的(可見應該可以通過調(diào)用時添加選項控制排序方式),可以看到在上面格式化控制的表格中也有排序信息,每個sortindx對應一種指標,默認配置存放在系統(tǒng)配置文件中,在初始化時讀入。
#define SYS_RCFILESPEC "/etc/toprc"// read part of an old-style config in /etc/toprc
fd = open(SYS_RCFILESPEC, O_RDONLY);
summary_show()函數(shù),逐個重點來看
// sleep for half a secondtv.tv_sec = 0;tv.tv_usec = 500000;select(0, NULL, NULL, NULL, &tv); // ought to loop until done
select函數(shù)這個我乍一眼沒想起來是哪用過,看注釋明白功能后想起來Socket編程里面用過這個函數(shù)。
//等待用戶連接請求或用戶數(shù)據(jù)到來或會話socke可發(fā)送數(shù)據(jù)
if((this->numOfSocketSignaled = select(0,&this->rfds,&this->wfds,NULL,NULL)) == SOCKET_ERROR){ //select函數(shù)返回有可讀或可寫的socket的總數(shù),保存在rtn里.最后一個參數(shù)設定等待時間,如為NULL則為阻塞模式cout << "select() failed with error!\n";return -1;
}
誒我這個菜雞。。。腦子里只想到用sleep函數(shù)去實現(xiàn)。。。
然后是核心的進程函數(shù)更新
p_table = procs_refresh(p_table, Frames_libflags);
而在更新進程的眾多調(diào)用函數(shù)中核心的又是
if (unlikczely(!(ptsk = readproc(PT, table[curmax])))) break;
如此跳轉(zhuǎn)到
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都是默認設置
查看其源碼發(fā)現(xiàn)似乎非常簡單。。。似乎沒有很特別的地方了。
//
// 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;
}
后面便是系統(tǒng)信息的函數(shù)了
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;}
大概看了一遍功能函數(shù),解析字符串–>在表中對應位置轉(zhuǎn)化數(shù)據(jù)存入–>計算
還是沒啥復雜的,不過這個寫法值得借鑒一哈,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';//超過緩存區(qū)大小(應該是一種特殊情況,和任務名的大小無關)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;//將:后的數(shù)據(jù)轉(zhuǎn)為長整型輸出,同時將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了,發(fā)現(xiàn)可以直接用readproc函數(shù)獲取進程所有信息,以為課設不能這么干呢,原來是要求這么干的。這樣top指令所用的頭文件系統(tǒng)函數(shù)似乎都可以直接調(diào)用(這樣想來還是很科學的,時空效率上就有了更高的保障)。
總結
- 上一篇: P2163 [SHOI2007]园丁的烦
- 下一篇: [kuangbin]各种各样的题单