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中继续阅读剩余的代码。

标签:none

添加新评论

captcha
请输入验证码