psproc源码阅读 - 1
好久没有写文章了(2021年一年都没写……),随便开点新坑,从简单的代码来读起。
从psproc工程下的ps代码开始。
display.c:
/***** no comment */
int main(int argc, char *argv[]) {
atexit(close_stdout);
myname = strrchr(*argv, '/');
if (myname) ++myname;
else myname = *argv;
Hertz = procps_hertz_get();
setlocale (LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
setenv("TZ", ":/etc/localtime", 0);
先从main看起,首先,atexit函数设置close_stdout为其退出时的处理函数,这个库函数确实少见。
ATEXIT(3) Linux Programmer's Manual ATEXIT(3)
NAME
atexit - register a function to be called at normal process termination
SYNOPSIS
#include <stdlib.h>
int atexit(void (*function)(void));
然后搜索argv[0],并找到"/"之后的内容作为自己的文件名,如果没有就直接用argv[0]。
procps_hertz_get用于获取CPU的时钟频率(sysconf(_SC_CLK_TCK)),如果获取失败返回100。
然后设置区域信息,并设置环境变量TZ为/etc/localtime。
然后是一段信号处理的函数。将一些黑名单信号以外的信号传递给singal_handler。
#ifdef DEBUG
init_stack_trace(argv[0]);
#else
do {
struct sigaction sa;
int i = 32;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigfillset(&sa.sa_mask);
while(i--) switch(i) {
default:
sigaction(i,&sa,NULL);
case 0:
case SIGCONT:
case SIGINT: /* ^C */
case SIGTSTP: /* ^Z */
case SIGTTOU: /* see stty(1) man page */
case SIGQUIT: /* ^\ */
case SIGPROF: /* profiling */
case SIGKILL: /* can not catch */
case SIGSTOP: /* can not catch */
case SIGWINCH: /* don't care if window size changes */
case SIGURG: /* Urgent condition on socket (4.2BSD) */
;
}
} while (0);
#endif
接下来是几个相对比较重要的处理代码。
reset_global(); /* must be before parser */
arg_parse(argc,argv);
/* check for invalid combination of arguments */
arg_check_conflicts();
/* arg_show(); */
trace("screen is %ux%u\n",screen_cols,screen_rows);
/* printf("sizeof(proc_t) is %d.\n", sizeof(proc_t)); */
trace("======= ps output follows =======\n");
首先是reset_global()。global.c:reset_global用于初始化所有的环境变量。我们依次阅读它的代码。
global.c
/************ Call this to reinitialize everything ***************/
void reset_global(void) {
proc_t *p;
int i;
reset_selection_list();
>>
static void reset_selection_list(void) {
selection_node *old;
selection_node *walk = selection_list;
if(selection_list == (selection_node *)0xdeadbeef) {
selection_list = NULL;
return;
}
while(walk) {
old = walk;
walk = old->next;
free(old->u);
free(old);
}
selection_list = NULL;
}
它首先调用reset_selection_list,如果section_list无效(0xdeadbeef)则置空,如果有内容,则挨个释放并将其置空。这个值并不是编译器或者内存管理器置的,而是它自己初始化的时候设置的:
selection_node *selection_list = (selection_node *)0xdeadbeef;
回到reset_global中,
/************ Call this to reinitialize everything ***************/
void reset_global(void) {
proc_t *p;
int i;
reset_selection_list();
// --- <pids> interface --------------------------------------------------
if (!Pids_items)
Pids_items = xcalloc(PIDSITEMS, sizeof(enum pids_item));
for (i = 0; i < PIDSITEMS; i++)
Pids_items[i] = PIDS_noop;
if (!Pids_info) {
if (procps_pids_new(&Pids_info, Pids_items, i)) {
fprintf(stderr, _("fatal library error, context\n"));
exit(EXIT_FAILURE);
}
}
Pids_items[0] = PIDS_TTY;
procps_pids_reset(Pids_info, Pids_items, 1);
if (!(p = fatal_proc_unmounted(Pids_info, 1))) {
fprintf(stderr, _("fatal library error, lookup self\n"));
exit(EXIT_FAILURE);
}
接下来是另一个重要的结构,Pids_items,它是一个全局变量,类型为“enum pids_item*”。 xcalloc是psproc自己的wrap,就是calloc加了个检查,可以认为二者相同。PIDSITEMS为70,注释里写道70是拍脑袋的数字。
因此首先,它为Pids_items分配70个pids_item,然后将其初始化为“PIDS_noop”。PIDS_noop是enum pids_item的第一项。
接下来是对Pids_info的初始化。pids.c:procps_pids_new用来初始化Pids_info结构体。这也是一个大型函数,我们把它抽出来看:
pids.c
PROCPS_EXPORT int procps_pids_new (
struct pids_info **info,
enum pids_item *items,
int numitems)
{
struct pids_info *p;
double uptime_secs;
int pgsz;
#ifdef ITEMTABLE_DEBUG
... (Removed) ...
#endif
if (info == NULL || *info != NULL)
return -EINVAL;
if (!(p = calloc(1, sizeof(struct pids_info))))
return -ENOMEM;
/* if we're without items or numitems, a later call to
procps_pids_reset() will become mandatory */
if (items && numitems) {
if (pids_items_check_failed(items, numitems)) {
free(p);
return -EINVAL;
}
// allow for our PIDS_logical_end
p->maxitems = numitems + 1;
if (!(p->items = calloc(p->maxitems, sizeof(enum pids_item)))) {
free(p);
return -ENOMEM;
}
memcpy(p->items, items, sizeof(enum pids_item) * numitems);
p->items[numitems] = PIDS_logical_end;
p->curitems = p->maxitems;
pids_libflags_set(p);
}
if (!(p->hist = calloc(1, sizeof(struct history_info)))
|| (!(p->hist->PHist_new = calloc(NEWOLD_INIT, sizeof(HST_t))))
|| (!(p->hist->PHist_sav = calloc(NEWOLD_INIT, sizeof(HST_t))))) {
free(p->items);
if (p->hist) {
free(p->hist->PHist_sav); // this & next might be NULL ...
free(p->hist->PHist_new);
free(p->hist);
}
free(p);
return -ENOMEM;
}
p->hist->HHist_siz = NEWOLD_INIT;
pids_config_history(p);
pgsz = getpagesize();
while (pgsz > 1024) {
pgsz >>= 1;
p->pgs2k_shift++;
}
p->hertz = procps_hertz_get();
// in case 'fatal_proc_unmounted' wasn't called and /proc isn't mounted
if (0 >= procps_uptime(&uptime_secs, NULL))
p->boot_seconds = uptime_secs;
numa_init();
p->fetch.results.counts = &p->fetch.counts;
p->refcount = 1;
*info = p;
return 0;
} // end: procps_pids_new
首先是为struct pids_info *p;
赋予初始值的p = calloc(1, sizeof(struct pids_info))
,然后对传入的items、numitems进行处理。
if (pids_items_check_failed(items, numitems)) {
free(p);
return -EINVAL;
}
调用的pids_items_check_failed用于检查传入的item是否合法。传入的如果不是enum pids_item*
指针(而是enum
的值),则在这里返回错误(<0x8000的值认为非法)。合法的话,检查是不是每项都在enum范围内。
static inline int pids_items_check_failed (
enum pids_item *items,
int numitems)
{
int i;
/* if an enum is passed instead of an address of one or more enums, ol' gcc
* will silently convert it to an address (possibly NULL). only clang will
* offer any sort of warning like the following:
*
* warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum pids_item *'
* if (procps_pids_new(&info, PIDS_noop, 3) < 0)
* ^~~~~~~~~~~~~~~~
*/
if (numitems < 1
|| (void *)items < (void *)0x8000) // twice as big as our largest enum
return 1;
for (i = 0; i < numitems; i++) {
// a pids_item is currently unsigned, but we'll protect our future
if (items[i] < 0)
return 1;
if (items[i] >= PIDS_logical_end) {
return 1;
}
}
return 0;
} // end: pids_items_check_failed
检查通过以后,分配对应的项目并将值复制到p中。
// allow for our PIDS_logical_end
p->maxitems = numitems + 1;
if (!(p->items = calloc(p->maxitems, sizeof(enum pids_item)))) {
free(p);
return -ENOMEM;
}
memcpy(p->items, items, sizeof(enum pids_item) * numitems);
p->items[numitems] = PIDS_logical_end;
p->curitems = p->maxitems;
pids_libflags_set(p);
然后是另一部分的初始化。如果hist的任何一部分初始化失败了,则释放里面已申请的内容。pids_config_history用于初始化HHash_one和HHash_two(初始化为HHash_nul),并修改PHash_save为HHash_one,PHash_new为HHash_two。
if (!(p->hist = calloc(1, sizeof(struct history_info)))
|| (!(p->hist->PHist_new = calloc(NEWOLD_INIT, sizeof(HST_t))))
|| (!(p->hist->PHist_sav = calloc(NEWOLD_INIT, sizeof(HST_t))))) {
free(p->items);
if (p->hist) {
free(p->hist->PHist_sav); // this & next might be NULL ...
free(p->hist->PHist_new);
free(p->hist);
}
free(p);
return -ENOMEM;
}
p->hist->HHist_siz = NEWOLD_INIT;
pids_config_history(p);
pgsz = getpagesize();
while (pgsz > 1024) {
pgsz >>= 1;
p->pgs2k_shift++;
}
p->hertz = procps_hertz_get();
最后是一些收尾的。procps_uptime用于读取/proc/uptime来获取系统的uptime和idle time。numa_init用于初始化numa(Non Uniform Memory Access, libnuma.so/libnuma.so.1)。
// in case 'fatal_proc_unmounted' wasn't called and /proc isn't mounted
if (0 >= procps_uptime(&uptime_secs, NULL))
p->boot_seconds = uptime_secs;
numa_init();
p->fetch.results.counts = &p->fetch.counts;
p->refcount = 1;
*info = p;
return 0;
} // end: procps_pids_new
这个大函数终于结束了。回到我们最开始的reset_global的后半部分中:
Pids_items[0] = PIDS_TTY;
procps_pids_reset(Pids_info, Pids_items, 1);
if (!(p = fatal_proc_unmounted(Pids_info, 1))) {
fprintf(stderr, _("fatal library error, lookup self\n"));
exit(EXIT_FAILURE);
}
不过我们只能在这里短暂停留,因为procps_pids_reset也是一个大函数。
pids.c:
PROCPS_EXPORT int procps_pids_reset (
struct pids_info *info,
enum pids_item *newitems,
int newnumitems)
{
if (info == NULL || newitems == NULL)
return -EINVAL;
if (pids_items_check_failed(newitems, newnumitems))
return -EINVAL;
pids_cleanup_stacks_all(info);
pids_clenaup_stacks_all函数的定义如下,它对info->extends的ext链表中的每个项目都调用pids_cleanp_stack。目标是info->extends->ext[..]->stacks[i]->head。 stacks顾名思义是一个栈结构。pids_clean_stack对每个项目查找Item_table中对应的freefunc,并使用freefunc来释放它们。
static inline void pids_cleanup_stacks_all (
struct pids_info *info)
{
struct stacks_extent *ext = info->extents;
int i;
while (ext) {
for (i = 0; ext->stacks[i]; i++)
pids_cleanup_stack(ext->stacks[i]->head);
ext = ext->next;
};
} // end: pids_cleanup_stacks_all
>>>
static inline void pids_cleanup_stack (
struct pids_result *this)
{
for (;;) {
enum pids_item item = this->item;
if (item >= PIDS_logical_end)
break;
if (Item_table[item].freefunc)
Item_table[item].freefunc(this);
this->result.ull_int = 0;
++this;
}
} // end: pids_cleanup_stack
Item_table的内容类似:
static struct {
SET_t setsfunc; // the actual result setting routine
#ifdef ITEMTABLE_DEBUG
int enumnumb; // enumerator (must match position!)
char *enum2str; // enumerator name as a char* string
#endif
unsigned oldflags; // PROC_FILLxxxx flags for this item
FRE_t freefunc; // free function for strings storage
QSR_t sortfunc; // sort cmp func for a specific type
int needhist; // a result requires history support
char *type2str; // the result type as a string value
} Item_table[] = {
/* setsfunc oldflags freefunc sortfunc needhist type2str
--------------------- ---------- --------- ------------- -------- ----------- */
{ RS(noop), 0, NULL, QS(noop), 0, TS_noop }, // user only, never altered
{ RS(extra), 0, NULL, QS(ull_int), 0, TS_noop }, // user only, reset to zero
{ RS(ADDR_CODE_END), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
{ RS(ADDR_CODE_START), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
{ RS(ADDR_CURR_EIP), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
{ RS(ADDR_CURR_ESP), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
{ RS(ADDR_STACK_START), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
这些freefunc实际上也就是对不同类型的东西调用其free。其实这里就是用c实现了一套接口,谁叫这不是用c++写的呢。
static void freNAME(str) (struct pids_result *R) {
if (R->result.str) free(R->result.str);
}
static void freNAME(strv) (struct pids_result *R) {
if (R->result.strv && *R->result.strv) free(*R->result.strv);
}
再回到procps_pid_reset中:
/* shame on this caller, they didn't change anything. and unless they have
altered the depth of the stacks we're not gonna change anything either! */
if (info->curitems == newnumitems + 1
&& !memcmp(info->items, newitems, sizeof(enum pids_item) * newnumitems))
return 0;
if (info->maxitems < newnumitems + 1) {
while (info->extents) {
struct stacks_extent *p = info->extents;
info->extents = p->next;
free(p);
};
if (info->get_ext) {
pids_oldproc_close(&info->get_PT);
info->get_ext = NULL;
}
if (info->fetch.anchor) {
free(info->fetch.anchor);
info->fetch.anchor = NULL;
}
// allow for our PIDS_logical_end
info->maxitems = newnumitems + 1;
if (!(info->items = realloc(info->items, sizeof(enum pids_item) * info->maxitems)))
return -ENOMEM;
}
memcpy(info->items, newitems, sizeof(enum pids_item) * newnumitems);
info->items[newnumitems] = PIDS_logical_end;
// account for above PIDS_logical_end
info->curitems = newnumitems + 1;
// if extents were freed above, this next guy will have no effect
// so we'll rely on pids_stacks_alloc() to itemize ...
pids_itemize_stacks_all(info);
pids_libflags_set(info);
return 0;
} // end: procps_pids_reset
剩余的代码相对就没那么复杂了。作者抽风写的注释也能解释很多,首先是第一个if判断,当调用时items的数量没有变,且内容也没有变的时候就什么都不做。如果当前的容量已经不够了,把栈区多余的内容释放,停止扫描进程表,释放fetch.anchor,并扩展max_items,拷贝newitems到原始的内容中。最后,调用pids_itemize_stacks_all。老实说这个函数在干什么我暂时也不太清楚,先留着坑后面再看看(可能是给top用的,不是给ps用的)。最后,设置flags并完成函数功能。
回到reset_global,继续看下一个大函数fatal_proc_unmounted。
if (!(p = fatal_proc_unmounted(Pids_info, 1))) {
fprintf(stderr, _("fatal library error, lookup self\n"));
exit(EXIT_FAILURE);
}
fatal_proc_unmounted为每个pids结构分配一个栈结构,并初始化相关结构体。不细看了,后面碰到有用相关结构的时候再回头看看。在pids接口的相关内容处理完成后,reset_global接下来的内容比较轻松:
set_screen_size();
set_personality();
all_processes = 0;
bsd_c_option = 0;
bsd_e_option = 0;
cached_euid = geteuid();
cached_tty = PIDS_VAL(0, s_int, p, Pids_info);
/* forest_prefix must be all zero because of POSIX */
forest_type = 0;
format_flags = 0; /* -l -f l u s -j... */
format_list = NULL; /* digested formatting options */
format_modifiers = 0; /* -c -j -y -P -L... */
header_gap = -1; /* send lines_to_next_header to -infinity */
header_type = HEAD_SINGLE;
include_dead_children = 0;
lines_to_next_header = 1;
negate_selection = 0;
page_size = getpagesize();
running_only = 0;
selection_list = NULL;
simple_select = 0;
sort_list = NULL;
thread_flags = 0;
unix_f_option = 0;
user_is_number = 0;
wchan_is_number = 0;
/* Translation Note:
. The following translatable word will be used to recognize the
. user's request for help text. In other words, the translation
. you provide will alter program behavior.
.
. It must be limited to 15 characters or less.
*/
the_word_help = _("help");
}
基本就是把全局变量都给初始化了。psproc的这些全局变量命名长得和局部变量一样挺讨厌的,好在软件标注看起来还不那么难受。
还记得我们是从main过来的么……难受的reset_global看完了以后,回到main中继续阅读剩余的代码。