psproc 源码阅读 - 5

剩余的代码中,最重要的是show_one_proc,它读取结构体并解析其中的一项,调用其print函数打印到屏幕上。

/********** show one process (NULL proc prints header) **********/

//#define SPACE_AMOUNT page_size
#define SPACE_AMOUNT 144

static char *saved_outbuf;

void show_one_proc(const proc_t *restrict const p, const format_node *restrict fmt) {
    /* unknown: maybe set correct & actual to 1, remove +/- 1 below */
    int correct  = 0;  /* screen position we should be at */
    int actual   = 0;  /* screen position we are at */
    int amount   = 0;  /* amount of text that this data is */
    int leftpad  = 0;  /* amount of space this column _could_ need */
    int space    = 0;  /* amount of space we actually need to print */
    int dospace  = 0;  /* previous column determined that we need a space */
    int legit    = 0;  /* legitimately stolen extra space */
    int sz       = 0;  /* real size of data in outbuffer */
    int tmpspace = 0;
    char *restrict const outbuf = saved_outbuf;
    static int did_stuff = 0;  /* have we ever printed anything? */

p为-1时是其最后一次调用。如果仍然有内容,则打印剩余的内容。

    if(-1==(long)p) {   /* true only once, at the end */
        if(did_stuff) return;
        /* have _never_ printed anything, but might need a header */
        if(!--lines_to_next_header) {
            lines_to_next_header = header_gap;
            show_one_proc(NULL,fmt);
        }
        /* fprintf(stderr, "No processes available.\n"); */  /* legal? */
        exit(1);
    }

如果是其他情况,则生成这样的递归栈输出fmt。fmt(format node)就是那一组预定义的标头和格式化的数组。

    if(p) { /* not header, maybe we should call ourselves for it */
        if(!--lines_to_next_header) {
            lines_to_next_header = header_gap;
            show_one_proc(NULL,fmt);
        }
    }
    did_stuff = 1;
    if(active_cols>(int)OUTBUF_SIZE) fprintf(stderr,_("fix bigness error\n"));

    /* print row start sequence */
    for(;;) {
        legit = 0; 

        if(fmt->next) {
            max_rightward = fmt->width;
            tmpspace = 0;
        } else {
            tmpspace = correct-actual;
            if (tmpspace<1) {
                tmpspace = dospace;
                max_rightward = active_cols-actual-tmpspace;
            } else {
                max_rightward = active_cols - ( (correct>actual) ? correct : actual );
            }
        }
        if(max_rightward <= 0) max_rightward = 0;
        else if(max_rightward >= OUTBUF_SIZE) max_rightward = OUTBUF_SIZE-1;

        max_leftward  = fmt->width + actual - correct; /* TODO check this */
        if(max_leftward <= 0) max_leftward = 0;
        else if(max_leftward >= OUTBUF_SIZE) max_leftward = OUTBUF_SIZE-1;

计算完位置后,调用fmt的print函数来处理。举例阅读pr_wchan。

static int pr_wchan(char *restrict const outbuf, const proc_t *restrict const pp) {
    const char *w;
    size_t len;
    setREL1(WCHAN_NAME)  //<-- 如果没有设置outbuf,设置rel_WCHAN_NAME,设置完会退出。如果outbuf有值则不管。 这里outbuf是由saved_outbuf(= outbuf + SPACE_AMOUNT == 144,还记得最早的时候初始化的那个带保护页的区域吗……)传来的,因此有值。
    w = rSv(WCHAN_NAME, str, pp); //<-- rSv复习一下,就是pp->head[rel_WCHAN_NAME].result.str。

这个属性是由setDECL设置的。

setDECL(WCHAN_NAME)     {
    freNAME(str)(R);
    if (!(R->result.str = strdup(lookup_wchan(P->tid)))) I->seterr = 1;;
}

回到原函数中,剩余的就是将数据拷贝到outbuf中。

    len = strlen(w);
    if(len>max_rightward) len=max_rightward;
    memcpy(outbuf, w, len);
    outbuf[len] = '\0';
    return len;
}

回到上一层的函数中,如果没有fmt->pr则将fmt->name追加到outbuf里。

        /* prepare data and calculate leftpad */
        if(p && fmt->pr) amount = (*fmt->pr)(outbuf,p);
        else amount = snprintf(outbuf, OUTBUF_SIZE, "%s", fmt->name); /* AIX or headers */

并补足末尾0。

        if(amount < 0) outbuf[amount = 0] = '\0';
        else if(amount >= OUTBUF_SIZE) outbuf[amount = OUTBUF_SIZE-1] = '\0';

        switch((fmt->flags) & CF_JUST_MASK) {
        case 0:  /* for AIX, assigned outside this file */
            leftpad = 0;
            break;
        case CF_LEFT:          /* bad */
            leftpad = 0;
            break;
        case CF_RIGHT:     /* OK */
            leftpad = fmt->width - amount;
            if(leftpad < 0) leftpad = 0;
            break;
        case CF_SIGNAL:
            /* if the screen is wide enough, use full 16-character output */
            if(wide_signals) {
                leftpad = 16 - amount;
                legit = 7;
            } else {
                leftpad =  9 - amount;
            }
            if(leftpad < 0) leftpad = 0;
            break;
        case CF_USER:       /* bad */
            leftpad = fmt->width - amount;
            if(leftpad < 0) leftpad = 0;
            if(!user_is_number) leftpad = 0;
            break;
        case CF_WCHAN:       /* bad */
            if(wchan_is_number) {
                leftpad = fmt->width - amount;
                if(leftpad < 0) leftpad = 0;
                break;
            } else {
                if ((active_cols-actual-tmpspace)<1)
                    outbuf[1] = '\0';  /* oops, we (mostly) lose this column... */
                leftpad = 0;
                break;
            }
        case CF_UNLIMITED:
        {
            if(active_cols-actual-tmpspace < 1)
                outbuf[1] = '\0';    /* oops, we (mostly) lose this column... */
            leftpad = 0;
            break;
        }
        default:
            fprintf(stderr, _("bad alignment code\n"));
            break;
        }
        /* At this point:
         *
         * correct   from previous column
         * actual    from previous column
         * amount    not needed (garbage due to chopping)
         * leftpad   left padding for this column alone (not make-up or gap)
         * space     not needed (will recalculate now)
         * dospace   if we require space between this and the prior column
         * legit     space we were allowed to steal, and thus did steal
         */
        space = correct - actual + leftpad;
        if(space<1) space=dospace;
        if(space>SPACE_AMOUNT) space=SPACE_AMOUNT;  // only so much available

        /* real size -- don't forget in 'amount' is number of cells */
        outbuf[OUTBUF_SIZE-1] = '\0';
        sz = strlen(outbuf);

        /* print data, set x position stuff */
        if(!fmt->next) {
            /* Last column. Write padding + data + newline all together. */
            outbuf[sz] = '\n';
            fwrite(outbuf-space, space+sz+1, 1, stdout);
            break;
        }
        /* Not the last column. Write padding + data together. */
        fwrite(outbuf-space, space+sz, 1, stdout);
        actual  += space+amount;
        correct += fmt->width;
        correct += legit;        /* adjust for SIGNAL expansion */
        if(fmt->pr && fmt->next->pr) { /* neither is AIX filler */
            correct++;
            dospace = 1;
        } else {
            dospace = 0;
        }
        fmt = fmt->next;
        /* At this point:
         *
         * correct   screen position we should be at
         * actual    screen position we are at
         * amount    not needed
         * leftpad   not needed
         * space     not needed
         * dospace   if have determined that we need a space next time
         * legit     not needed
         */
    }
}

psproc 源码阅读 - 4

回到main中,还剩最后一点点代码:

    lists_and_needs(); 
    finalize_stacks(); //<===

    if(forest_type || sort_list) fancy_spew(); 
    else simple_spew(); /* no sort, no forest */
    show_one_proc((proc_t *)-1,format_list); /* no output yet? */

    procps_pids_unref(&Pids_info);
    return 0;
}

finalize_stacks是一个……基本由宏组成的函数。中间重复的宏太多了我就删掉了。

static void finalize_stacks (void)
{
    format_node *f_node;
    sort_node *s_node;

#if (PIDSITEMS < 60)
# error PIDSITEMS (common.h) should be at least 60!
#endif

    /* first, ensure minimum result structures for items
       which may or may not actually be displayable ... */
    Pids_index = 0;

    // needed by for selections
    chkREL(CMD)
    chkREL(ID_EGID)
    ………………
    chkREL(extra)
    chkREL(noop)

    // now accommodate any results not yet satisfied
    f_node = format_list;
    while (f_node) {
        (*f_node->pr)(NULL, NULL);
        f_node = f_node->next;
    }
    s_node = sort_list;
    while (s_node) {
        if (s_node->xe) (*s_node->xe)(NULL, NULL);
        s_node = s_node->next;
    }

    procps_pids_reset(Pids_info, Pids_items, Pids_index);
}

其中,chkREL的定义如下:

#define namREL(e) rel_ ## e
#define makEXT(e) extern int namREL(e);
#define makREL(e) int namREL(e) = -1;
#define chkREL(e) if (namREL(e) < 0) { \
      Pids_items[Pids_index] = PIDS_ ## e; \
      namREL(e) = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop; }

展开一下就是:

   if(rel_XX < 0) {
     Pids_items[Pids_index] = PIDS_XX;
     rel_XX = (Pids_index < PIDSITEMS) ? Pids_index++ : rel_noop;
   }

chkREL实际做的事情就是初始化Pids_items中各不同的rel_XXX项。然后,对format_list中的每一项,都调用其pr()来处理。pr其实就是print函数,调用snprintf向其outbuf来输出内容。然后,对sort_list中的每一项,调用其xe()来处理。最后,调用procps_pids_reset。之前看过一次就不再重复了。

回到main中,下一个函数是fancy_spew,当然仅当开启forest_type / sort_list后才调用。

    if(forest_type || sort_list) fancy_spew();  //<---
    else simple_spew(); /* no sort, no forest */
    show_one_proc((proc_t *)-1,format_list); /* no output yet? */

    procps_pids_unref(&Pids_info);
    return 0;
}

fancy_spew定义如下:

/***** sorted or forest */
static void fancy_spew(void) {
    struct pids_fetch *pidread;
    enum pids_fetch_type which;
    proc_t *buf;
    int i, n = 0;

    which = (thread_flags & TF_loose_tasks)
            ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;

    pidread = procps_pids_reap(Pids_info, which);
    if (!pidread || !pidread->counts->total) {
        fprintf(stderr, _("fatal library error, reap\n"));
        exit(EXIT_FAILURE);
    }
    processes = xcalloc(pidread->counts->total, sizeof(void*));
    for (i = 0; i < pidread->counts->total; i++) {
        buf = pidread->stacks[i];
        value_this_proc_pcpu(buf);
        if (want_this_proc(buf))
            processes[n++] = buf;
    }
    if (n) {
        if(forest_type) prep_forest_sort();
        while(sort_list) {
            procps_pids_sort(Pids_info, processes, n, sort_list->sr, sort_list->reverse);
            sort_list = sort_list->next;
        }
        if(forest_type) show_forest(n);
        else show_proc_array(n);
    }
    free(processes);
}

调用的第一个函数是procps_pids_reap。这个函数是一个重要的信息处理函数。它调用pids_oldproc_open。

/* procps_pids_reap():
 *
 * Harvest all the available tasks/threads and provide the result
 * stacks along with a summary of the information gathered.
 *
 * Returns: pointer to a pids_fetch struct on success, NULL on error.
 */
PROCPS_EXPORT struct pids_fetch *procps_pids_reap (
    struct pids_info *info,
    enum pids_fetch_type which)
{
    int rc;

    errno = EINVAL;
    if (info == NULL)
        return NULL;
    if (which != PIDS_FETCH_TASKS_ONLY && which != PIDS_FETCH_THREADS_TOO)
        return NULL;
    /* with items & numitems technically optional at 'new' time, it's
       expected 'reset' will have been called -- but just in case ... */
    if (!info->curitems)
        return NULL;
    errno = 0;

    if (!pids_oldproc_open(&info->fetch_PT, info->oldflags))
        return NULL;
    info->read_something = which ? readeither : readproc;

    rc = pids_stacks_fetch(info);

    pids_oldproc_close(&info->fetch_PT);
    // we better have found at least 1 pid
    return (rc > 0) ? &info->fetch.results : NULL;
} // end: procps_pids_reap

pids_oldproc_open定义如下,重要的一眼就可以看出来,openproc函数。

static inline int pids_oldproc_open (
    PROCTAB **this,
    unsigned flags,
    ...)
{
    va_list vl;
    int *ids;
    int num = 0;

    if (*this == NULL) {
        va_start(vl, flags);
        ids = va_arg(vl, int*);
        if (flags & PROC_UID) num = va_arg(vl, int);
        va_end(vl);
        if (NULL == (*this = openproc(flags, ids, num)))
            return 0;
    }
    return 1;
} // end: pids_oldproc_open

openproc的定义如下,函数比较长,重要的内容我们分段切入阅读。

// initiate a process table scan
PROCTAB *openproc(unsigned flags, ...) {
    va_list ap;
    struct stat sbuf;
    static __thread int did_stat;
    PROCTAB *PT = calloc(1, sizeof(PROCTAB));

    if (!PT)
        return NULL;
    if (!did_stat) {
        task_dir_missing = stat("/proc/self/task", &sbuf);
        did_stat = 1;
    }

这里注册一些处理函数。

    PT->taskdir = NULL;
    PT->taskdir_user = -1;
    PT->taskfinder = simple_nexttid;
    PT->taskreader = simple_readtask;

    PT->reader = simple_readproc;
    if (flags & PROC_PID) {
        PT->procfs = NULL;
        PT->finder = listed_nextpid;
    } else {
        PT->procfs = opendir("/proc");
        if (!PT->procfs) {
            free(PT);
            return NULL;
        }
        PT->finder = simple_nextpid;
    }
    PT->flags = flags;

如果传入的内容包含一组pid/uid则这里读取它们。我们现在还没遇到这个情况,暂且不理。

    va_start(ap, flags);
    if (flags & PROC_PID)
        PT->pids = va_arg(ap, pid_t*);
    else if (flags & PROC_UID) {
        PT->uids = va_arg(ap, uid_t*);
        PT->nuid = va_arg(ap, int);
    }
    va_end(ap);

MAX_BUFSZ为1024 * 64 * 2字节。这里初始化src_buffer和dst_buffer(都是全局变量)。

    if (!src_buffer
            && !(src_buffer = malloc(MAX_BUFSZ))) {
        closedir(PT->procfs);
        free(PT);
        return NULL;
    }
    if (!dst_buffer
            && !(dst_buffer = malloc(MAX_BUFSZ))) {
        closedir(PT->procfs);
        free(src_buffer);
        free(PT);
        return NULL;
    }

    return PT;
}

两个buffer已经申请完成,回到上上一层的procps_pids_reap中。下一个调用的函数是pids_stacks_fetch。其代码如下:

static int pids_stacks_fetch (
    struct pids_info *info)
{
#define n_alloc  info->fetch.n_alloc
#define n_inuse  info->fetch.n_inuse
#define n_saved  info->fetch.n_alloc_save
    struct stacks_extent *ext;

最前方是一堆初始化的,这里先懒得看了,有需要后面再返回来阅读。STACKS_INIT的值是1024。

    // initialize stuff -----------------------------------
    if (!info->fetch.anchor) {
        if (!(info->fetch.anchor = calloc(STACKS_INIT, sizeof(void *))))
            return -1;
        if (!(ext = pids_stacks_alloc(info, STACKS_INIT)))
            return -1;       // here, errno was set to ENOMEM
        memcpy(info->fetch.anchor, ext->stacks, sizeof(void *) * STACKS_INIT);
        n_alloc = STACKS_INIT;
    }
    pids_toggle_history(info);
    memset(&info->fetch.counts, 0, sizeof(struct pids_counts));

之后就到具体的处理函数了。info->read_something由procps_pids_reap设置,如果fetch的是PIDS_FETCH_TASKS_ONLY则read_something是readeither,否则是readproc。这个标志由thread_flags决定(fancy_spew中设置)。

    // iterate stuff --------------------------------------
    n_inuse = 0;
    while (info->read_something(info->fetch_PT, &info->fetch_proc)) {

readeither,其代码如下。调用的函数也如注释中描述的那样,是一堆simple_函数。

//////////////////////////////////////////////////////////////////////////////////
// readeither: return a pointer to a proc_t filled with requested info about
// the next unique process or task available.  If no more are available,
// return a null pointer (boolean false).
proc_t *readeither (PROCTAB *restrict const PT, proc_t *restrict x) {
    static __thread proc_t skel_p;    // skeleton proc_t, only uses tid + tgid
    static __thread proc_t *new_p;    // for process/task transitions
    static __thread int canary, leader;
    char path[PROCPATHLEN];
    proc_t *ret;

    free_acquired(x);

    if (new_p) {
        if (new_p->tid != canary) new_p = NULL;
        goto next_task;
    }

next_proc:
    new_p = NULL;
    for (;;) {
        if (errno == ENOMEM) goto end_procs;
        // fills in the PT->path, plus skel_p.tid and skel_p.tgid
        if (!PT->finder(PT,&skel_p)) goto end_procs;       // simple_nextpid
        leader = skel_p.tid;
        if (!task_dir_missing) break;
        if ((ret = PT->reader(PT,x))) return ret;          // simple_readproc
    }

next_task:
    // fills in our path, plus x->tid and x->tgid
    if (!(PT->taskfinder(PT,&skel_p,x,path)))              // simple_nexttid
        goto next_proc;
    /* to avoid loss of some thread group leader data,
       we must check its base dir, not its 'task' dir! */
    if (x->tid == leader) ret = PT->reader(PT,x);          // simple_readproc
    else ret = PT->taskreader(PT,x,path);                  // simple_readtask
    if (!ret) goto next_proc;
    if (!new_p) {
        new_p = ret;
        canary = new_p->tid;
    }
    return ret;

end_procs:
    return NULL;
}

我们随便抽几个例子看一下。首先是simple_nextpid。它在/proc下读取下一个文件夹。

//////////////////////////////////////////////////////////////////////////////////
// This finds processes in /proc in the traditional way.
// Return non-zero on success.
static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
static __thread struct dirent *ent; /* dirent handle */
char *restrict const path = PT->path;
for (;;) {
ent = readdir(PT->procfs);
if(!ent || !ent->d_name[0]) return 0;
if(*ent->d_name > '0' && *ent->d_name <= '9') break;
}
p->tgid = strtoul(ent->d_name, NULL, 10);
p->tid = p->tgid;
snprintf(path, PROCPATHLEN, "/proc/%s", ent->d_name);
return 1;
}

而具体处理/proc信息的是simple_readproc,我们依旧分解着来读。

//////////////////////////////////////////////////////////////////////////////////
// This reads process info from /proc in the traditional way, for one process.
// The pid (tgid? tid?) is already in p, and a path to it in path, with some
// room to spare.
static proc_t *simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) {
    static __thread struct utlbuf_s ub = { NULL, 0 };    // buf for stat,statm,status
    static __thread struct stat sb;     // stat() buffer
    char *restrict const path = PT->path;
    unsigned flags = PT->flags;
    int rc = 0;

    if (stat(path, &sb) == -1)                  /* no such dirent (anymore) */
        goto next_proc;

    if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
        goto next_proc;                      /* not one of the requested uids */

这里遇到一个XinLN宏,用于测试列表的前N个项目中有没有某个类型的值。这里列表是PT->uids。

/* Test if item X of type T is present in the 0 terminated list L */
#   define XinL(T, X, L) ( {                    \
            T  x = (X), *l = (L);               \
            while (*l && *l != x) l++;          \
            *l == x;                            \
        } )

/* Test if item X of type T is present in the list L of length N */
#   define XinLN(T, X, L, N) ( {                \
            T x = (X), *l = (L);                \
            int i = 0, n = (N);                 \
            while (i < n && l[i] != x) i++;     \
            i < n && l[i] == x;                 \
        } )

回到原来的代码里。很快可以看到一个名为file2str的函数,用于从文件中读取数据,放到ub->buf里。

static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) {
#define buffGRW 1024
    char path[PROCPATHLEN];
    int fd, num, tot_read = 0, len;

    /* on first use we preallocate a buffer of minimum size to emulate
       former 'local static' behavior -- even if this read fails, that
       buffer will likely soon be used for another subdirectory anyway
       ( besides, with the calloc call we will never need use memcpy ) */
    if (ub->buf) ub->buf[0] = '\0';
    else {
        ub->buf = calloc(1, (ub->siz = buffGRW));
        if (!ub->buf) return -1;
    }
    len = snprintf(path, sizeof path, "%s/%s", directory, what);
    if (len <= 0 || (size_t)len >= sizeof path) return -1;
    if (-1 == (fd = open(path, O_RDONLY, 0))) return -1;
    while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) {
        tot_read += num;
        if (tot_read < ub->siz) break;
        if (ub->siz >= INT_MAX - buffGRW) {
            tot_read--;
            break;
        }
        if (!(ub->buf = realloc(ub->buf, (ub->siz += buffGRW)))) {
            close(fd);
            return -1;
        }
    };
    ub->buf[tot_read] = '\0';
    close(fd);
    if (tot_read < 1) return -1;
    return tot_read;
#undef buffGRW
}

回到外层函数,可以看到它读取stat,然后调用stat2proc(其他同)对读取到的数据进行处理。这里的处理都是psproc的核心功能,因此我们会挨个跟踪进去。

    p->euid = sb.st_uid;                        /* need a way to get real uid */
    p->egid = sb.st_gid;                        /* need a way to get real gid */

首先是stat2proc。stat文件形式类似:

$ cat /proc/13/stat
13 (bash) S 12 13 12 1025 0 0 0 0 0 0 21 59 120 489 20 0 1 0 34 213183188992 1011 18446744073709551615 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

stat2proc(是的我没贴代码……不然太多了)会去查找( ) 括号中的内容,这个是来自task_struct结构的进程名,最长只有15字节。为了处理进程名中的特殊字符,它还会对其进行简单替换,然后对剩余内容进行扫描(sscanf)并保存到结构体中。

    if (flags & PROC_FILLSTAT) {                // read /proc/#/stat
        if (file2str(path, "stat", &ub) == -1)
            goto next_proc;
        rc += stat2proc(ub.buf, p);
    }

接下来是io的内容,一个sscanf解决。

    if (flags & PROC_FILLIO) {                  // read /proc/#/io
        if (file2str(path, "io", &ub) != -1)
            io2proc(ub.buf, p);
    }

然后是读取smaps,不过是从smaps_rollup读取。这个文件在哪个系统里有我目前还不清楚。它解析该文件,然后把每一项都创建一个object存起来。

    if (flags & PROC_FILLSMAPS) {               // read /proc/#/smaps_rollup
        if (file2str(path, "smaps_rollup", &ub) != -1)
            smaps2proc(ub.buf, p);
    }

statm,一样是sscanf读取。

    if (flags & PROC_FILLMEM) {                 // read /proc/#/statm
        if (file2str(path, "statm", &ub) != -1)
            statm2proc(ub.buf, p);
    }

status,一样。pwcache_get_user调用getpwuid来获取信息。如果用户名过长,则只复制uid部分。

    if (flags & PROC_FILLSTATUS) {              // read /proc/#/status
        if (file2str(path, "status", &ub) != -1) {
            rc += status2proc(ub.buf, p, 1);
            if (flags & (PROC_FILL_SUPGRP & ~PROC_FILLSTATUS))
                rc += supgrps_from_supgids(p);
            if (flags & (PROC_FILL_OUSERS & ~PROC_FILLSTATUS)) {
                p->ruser = pwcache_get_user(p->ruid);
                p->suser = pwcache_get_user(p->suid);
                p->fuser = pwcache_get_user(p->fuid);
            }
            if (flags & (PROC_FILL_OGROUPS & ~PROC_FILLSTATUS)) {
                p->rgroup = pwcache_get_group(p->rgid);
                p->sgroup = pwcache_get_group(p->sgid);
                p->fgroup = pwcache_get_group(p->fgid);
            }
        }
    }

    // if multithreaded, some values are crap
    if(p->nlwp > 1)
        p->wchan = ~0ul;

    /* some number->text resolving which is time consuming */
    /* ( names are cached, so memcpy to arrays was silly ) */
    if (flags & PROC_FILLUSR)
        p->euser = pwcache_get_user(p->euid);
    if (flags & PROC_FILLGRP)
        p->egroup = pwcache_get_group(p->egid);

继续,environ部分。首先仍然是读取environ文件,但是如果失败了,则调用vectorize_dash_rc解析p->environ_v。

    if (flags & PROC_FILLENV)                   // read /proc/#/environ
        if (!(p->environ_v = file2strvec(path, "environ")))
            rc += vectorize_dash_rc(&p->environ_v);
    if (flags & PROC_EDITENVRCVT)
        rc += fill_environ_cvt(path, p);

这里出现了一个新函数,vectorize_dash_rc,把"-"转为一个“vector元素”保存在*vec中。

// This littl' guy just serves those true vectorized fields
// ( when a /proc source field didn't exist )
static int vectorize_dash_rc (char ***vec) {
    if (!(*vec = vectorize_this_str("-")))
        return 1;
    return 0;
}
char **vectorize_this_str (const char *src) {
#define pSZ  (sizeof(char*))
    char *cpy, **vec;
    size_t adj, tot;

    tot = strlen(src) + 1;                       // prep for our vectors
    if (tot < 1 || tot >= INT_MAX) tot = INT_MAX-1; // integer overflow?
    adj = (pSZ-1) - ((tot + pSZ-1) & (pSZ-1));   // calc alignment bytes
    cpy = calloc(1, tot + adj + (2 * pSZ));      // get new larger buffer
    if (!cpy) return NULL;                       // oops, looks like ENOMEM
    snprintf(cpy, tot, "%s", src);               // duplicate their string
    vec = (char**)(cpy + tot + adj);             // prep pointer to pointers
    *vec = cpy;                                  // point 1st vector to string
    *(vec+1) = NULL;                             // null ptr 'list' delimit
    return vec;                                  // ==> free(*vec) to dealloc
#undef pSZ
}

最后回来调用fill_environ_cvt。

// This routine reads an 'environ' for the designated proc_t and
// guarantees the caller a valid proc_t.environ pointer.
static int fill_environ_cvt (const char *directory, proc_t *restrict p) {
    dst_buffer[0] = '\0';
    if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "environ", ' '))
        escape_str(dst_buffer, src_buffer, MAX_BUFSZ);
    p->environ = strdup(dst_buffer[0] ? dst_buffer : "-");
    if (!p->environ)
        return 1;
    return 0;
}

它调用read_unvectored来解析数据。它的前半部分从文件中读取数据(这个单独封装成一个函数不好吗……重复看到好多次了)。如果出现“\n”或者“\0”,则改为“sep”(最后一个参数)。

// this is the former under utilized 'read_cmdline', which has been
// generalized in support of these new libproc flags:
//     PROC_EDITCGRPCVT, PROC_EDITCMDLCVT and PROC_EDITENVRCVT
static int read_unvectored(char *restrict const dst, unsigned sz, const char *whom, const char *what, char sep) {
    char path[PROCPATHLEN];
    int fd, len;
    unsigned n = 0;

    if(sz <= 0) return 0;
    if(sz >= INT_MAX) sz = INT_MAX-1;
    dst[0] = '\0';

    len = snprintf(path, sizeof(path), "%s/%s", whom, what);
    if(len <= 0 || (size_t)len >= sizeof(path)) return 0;
    fd = open(path, O_RDONLY);
    if(fd==-1) return 0;

    for(;;) {
        ssize_t r = read(fd,dst+n,sz-n);
        if(r==-1) {
            if(errno==EINTR) continue;
            break;
        }
        if(r<=0) break;  // EOF
        n += r;
        if(n==sz) {      // filled the buffer
            --n;         // make room for '\0'
            break;
        }
    }
    close(fd);
    if(n) {
        unsigned i = n;
        while(i && dst[i-1]=='\0') --i; // skip trailing zeroes
        while(i--)
            if(dst[i]=='\n' || dst[i]=='\0') dst[i]=sep;
        if(dst[n-1]==' ') dst[n-1]='\0';
    }
    dst[n] = '\0';
    return n;
}

处理结束后调用escape_str。MAX_BUFSZ是1024642。escape_str定义如下,用于标准化字符串。

static inline void esc_all (unsigned char *str) {
    unsigned char c;

    // if bad locale/corrupt str, replace non-printing stuff
    while (*str) {
        if ((c = ESC_tab[*str]) != '|')
            *str = c;
        ++str;
    }
}

static inline void esc_ctl (unsigned char *str, int len) {
    int i, n;

    for (i = 0; i < len; ) {
        // even with a proper locale, strings might be corrupt
        if ((n = UTF_tab[*str]) < 0 || i + n > len) {
            esc_all(str);
            return;
        }
        // and eliminate those non-printing control characters
        if (*str < 0x20 || *str == 0x7f)
            *str = '?';
        str += n;
        i += n;
    }
}

int escape_str (unsigned char *dst, const unsigned char *src, int bufsize) {
    static __thread int utf_sw = 0;
    int n;

    if (utf_sw == 0) {
        char *enc = nl_langinfo(CODESET);
        utf_sw = enc && strcasecmp(enc, "UTF-8") == 0 ? 1 : -1;
    }
    SECURE_ESCAPE_ARGS(dst, bufsize);
    n = snprintf(dst, bufsize, "%s", src);
    if (n < 0) {
        *dst = '\0';
        return 0;
    }
    if (n >= bufsize) n = bufsize-1;
    if (utf_sw < 0)
        esc_all(dst);
    else
        esc_ctl(dst, n);
    return n;
}

下一行,类似的做法,只不过处理cmdline。

    if (flags & PROC_FILLARG)                   // read /proc/#/cmdline
        if (!(p->cmdline_v = file2strvec(path, "cmdline")))
            rc += vectorize_dash_rc(&p->cmdline_v);
    if (flags & PROC_EDITCMDLCVT)
        rc += fill_cmdline_cvt(path, p);

fill_cmdline_cvt稍有不同。

// This routine reads a 'cmdline' for the designated proc_t, "escapes"
// the result into a single string while guaranteeing the caller a
// valid proc_t.cmdline pointer.
static int fill_cmdline_cvt (const char *directory, proc_t *restrict p) {
#define uFLG ( ESC_BRACKETS | ESC_DEFUNCT )
    if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "cmdline", ' '))
        escape_str(dst_buffer, src_buffer, MAX_BUFSZ);
    else
        escape_command(dst_buffer, p, MAX_BUFSZ, uFLG);
    p->cmdline = strdup(dst_buffer[0] ? dst_buffer : "?");
    if (!p->cmdline)
        return 1;
    return 0;
#undef uFLG
}

其中escape_command定义如下。这里将数据解析到pp->cmd中。pp是上一层的“p”,最早在stat中被设置。

// Reads /proc/*/stat files, being careful not to trip over processes with
// names like ":-) 1 2 3 4 5 6".
static int stat2proc (const char *S, proc_t *restrict P) {
    char buf[64], raw[64];
    //...............
        if (!P->cmd) {
            num = tmp - S;
            memcpy(raw, S, num);
            raw[num] = '\0';
            escape_str(buf, raw, sizeof(buf));
            if (!(P->cmd = strdup(buf))) return 1;    //<------------
        }



int escape_command (unsigned char *outbuf, const proc_t *pp, int bytes, unsigned flags) {
    int overhead = 0;
    int end = 0;

    if (flags & ESC_BRACKETS)
        overhead += 2;
    if (flags & ESC_DEFUNCT) {
        if (pp->state == 'Z') overhead += 10;    // chars in " <defunct>"
        else flags &= ~ESC_DEFUNCT;
    }
    if (overhead + 1 >= bytes) {
        // if no room for even one byte of the command name
        outbuf[0] = '\0';
        return 0;
    }
    if (flags & ESC_BRACKETS)
        outbuf[end++] = '['; 
    end += escape_str(outbuf+end, pp->cmd, bytes-overhead);  //<----从cmd拷贝到outbuf+end。
    // we want "[foo] <defunct>", not "[foo <defunct>]"
    if (flags & ESC_BRACKETS)
        outbuf[end++] = ']'; 
    if (flags & ESC_DEFUNCT) {
        memcpy(outbuf+end, " <defunct>", 10);  
        end += 10;
    }
    outbuf[end] = '\0';
    return end;  // bytes, not including the NUL
}

接下来是读取cgroup、oom_score/oom_score_adj/ns/sdlogin

    if ((flags & PROC_FILLCGROUP))              // read /proc/#/cgroup
        if (!(p->cgroup_v = file2strvec(path, "cgroup")))
            rc += vectorize_dash_rc(&p->cgroup_v);
    if (flags & PROC_EDITCGRPCVT)
        rc += fill_cgroup_cvt(path, p);

    if (flags & PROC_FILLOOM) {
        if (file2str(path, "oom_score", &ub) != -1)
            oomscore2proc(ub.buf, p);
        if (file2str(path, "oom_score_adj", &ub) != -1)
            oomadj2proc(ub.buf, p);
    }

    if (flags & PROC_FILLNS)                    // read /proc/#/ns/*
        procps_ns_read_pid(p->tid, &(p->ns));


    if (flags & PROC_FILLSYSTEMD)               // get sd-login.h stuff
        rc += sd2proc(p);

这里读取lxc container相关的内容。

    if (flags & PROC_FILL_LXC)                  // value the lxc name
        p->lxcname = lxc_containers(path);

    if (flags & PROC_FILL_LUID)                 // value the login user id
        p->luid = login_uid(path);

解析exe指向的路径。

    if (flags & PROC_FILL_EXE) {
        if (!(p->exe = readlink_exe(path)))
            rc += 1;
    }

以及最后一小部分内容。

    if (flags & PROC_FILLAUTOGRP)               // value the 2 autogroup fields
        autogroup_fill(path, p);

    if (rc == 0) return p;
    errno = ENOMEM;
next_proc:
    return NULL;
}

读取完成后回到readeither里,另一个重要的是simple_readtask。但它读取的基本一样,不同的是它从/proc/#/task/#下面读。最后重复这一步骤直到所有的被读完。

另一个函数是readproc,是一个简化版readeither它的定义如下

proc_t *readproc(PROCTAB *restrict const PT, proc_t *restrict p) {
    proc_t *ret;

    free_acquired(p);

    for(;;) {
        if (errno == ENOMEM) goto out;
        // fills in the path, plus p->tid and p->tgid
        if (!PT->finder(PT,p)) goto out;

        // go read the process data
        ret = PT->reader(PT,p);
        if(ret) return ret;
    }

out:
    return NULL;
}

回到外侧pids_stacks_fetch中,剩余的基本就是在整理数据,并做展示前的准备工作。

        if (!(n_inuse < n_alloc)) {
            n_alloc += STACKS_GROW;
            if (!(info->fetch.anchor = realloc(info->fetch.anchor, sizeof(void *) * n_alloc))
                    || (!(ext = pids_stacks_alloc(info, STACKS_GROW))))
                return -1;   // here, errno was set to ENOMEM
            memcpy(info->fetch.anchor + n_inuse, ext->stacks, sizeof(void *) * STACKS_GROW);
        }
        if (!pids_proc_tally(info, &info->fetch.counts, &info->fetch_proc))
            return -1;       // here, errno was set to ENOMEM
        if (!pids_assign_results(info, info->fetch.anchor[n_inuse++], &info->fetch_proc))
            return -1;       // here, errno was set to ENOMEM
    }
    /* while the possibility is extremely remote, the readproc.c (read_something) |
       simple_readproc and simple_readtask guys could have encountered this error |
       in which case they would have returned a NULL, thus ending our while loop. | */
    if (errno == ENOMEM)
        return -1;

    // finalize stuff -------------------------------------
    /* note: we go to this trouble of maintaining a duplicate of the consolidated |
             extent stacks addresses represented as our 'anchor' since these ptrs |
             are exposed to a user (um, not that we don't trust 'em or anything). |
             plus, we can NULL delimit these ptrs which we couldn't do otherwise. | */
    if (n_saved < n_inuse + 1) {
        n_saved = n_inuse + 1;
        if (!(info->fetch.results.stacks = realloc(info->fetch.results.stacks, sizeof(void *) * n_saved)))
            return -1;
    }
    memcpy(info->fetch.results.stacks, info->fetch.anchor, sizeof(void *) * n_inuse);
    info->fetch.results.stacks[n_inuse] = NULL;

    return n_inuse;     // callers beware, this might be zero !
#undef n_alloc
#undef n_inuse
#undef n_saved
} // end: pids_stacks_fetch

psproc源码阅读 - 3

接着上一篇的来,首先这个ARG_SYSV的花括号是真的很风骚,我也是第一次看到把case放在if的花括号里面的。anyway,先看这两个分支共同会到达的部分parse_bsd_option。

    case ARG_SYSV:
        if(!force_bsd) {  /* else go past case ARG_BSD */
            err = parse_sysv_option();
            break;

case ARG_BSD:
                if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad");
        }
        prefer_bsd_defaults = 1;
        err = parse_bsd_option();
        break;

BSD options的处理由一个大函数完成,这也是我们用ps时常用的语法。开头几句它检查命令的格式是否和设置冲突,然后对flag中每一个字符进行switch case来处理。

/************************* parse BSD options **********************/
static const char *parse_bsd_option(void) {
    const char *arg;
    const char *err;

    flagptr = ps_argv[thisarg];  /* assume we _have_ a '-' */
    if(flagptr[0]=='-') {
        if(!force_bsd) return _("cannot happen - problem #1");
    } else {
        flagptr--; /* off beginning, will increment before use */
        if(personality & PER_FORCE_BSD) {
            if(!force_bsd) return _("cannot happen - problem #2");
        } else {
            if(force_bsd) return _("second chance parse failed, not BSD or SysV");
        }
    }

    while(*++flagptr) {
        switch(*flagptr) {
        case '0' ... '9': /* end */

这里的处理没什么新意,挑几个之前没出现过的函数来读一下。

    case 'O': /* end */
        trace("O like o + defaults, add new columns after PID, also sort\n");
        arg=get_opt_arg();
        if(!arg) return _("format or sort specification must follow O");
        defer_sf_option(arg, SF_B_O);
        return NULL; /* can't have any more options */
        break;

首先是defer_sf_option。其实出现过了,但是当时我懒得看,现在看一下它在做什么。defer_sf_option是一个中等长度的函数,开头依然是熟悉的初始化,初始化要用到的sf_node结构体。支持的sort & format一共7种,在common.h中定义。

/* sorting & formatting */
/* U,B,G is Unix,BSD,Gnu and then there is the option itself */
#define SF_U_O      1
#define SF_U_o      2
#define SF_B_O      3
#define SF_B_o      4
#define SF_B_m      5       /* overloaded: threads, sort, format */
#define SF_G_sort   6
#define SF_G_format 7

/************ Main parser calls this to save lists for later **********/
/* store data for later and return 1 if arg looks non-standard */
int defer_sf_option(const char *arg, int source) {
    sf_node *sfn;
    char buf[16];
    int dist;
    const format_struct *fs;
    int need_item = 1;

    sfn = xmalloc(sizeof(sf_node));
    sfn->sf = strdup(arg);
    sfn->sf_code = source;
    sfn->s_cooked = NULL;
    sfn->f_cooked = NULL;
    sfn->next = sf_list;
    sf_list = sfn;

    if(source == SF_G_sort) have_gnu_sort = 1;

    /* Now try to find an excuse to ignore broken Unix98 parsing. */
    if(source != SF_U_o) return 1;    /* Wonderful! Already non-Unix98. */
    do {
        switch(*arg) {
        case ' ':
        case ',':
        case '\0':  /* no \t\n\r support in Unix98 */
            if(need_item) return 1;       /* something wrong */
            need_item=1;
            break;
        case '=':
            if(need_item) return 1;       /* something wrong */
            return 0;                     /* broken Unix98 parsing is required */
        default:
            if(!need_item) break;
            need_item=0;
            dist = strcspn(arg,", =");
            if(dist>15) return 1;         /* something wrong, sort maybe? */
            strncpy(buf,arg,dist);   /* no '\0' on end */
            buf[dist] = '\0';        /* fix that problem */
            fs = search_format_array(buf);
            if(!fs) return 1;             /* invalid spec, macro or sort maybe? */
            if(fs->vendor) return 1;      /* Wonderful! Legal non-Unix98 spec. */
        }
    } while (*++arg);

    return 0;                         /* boring, Unix98 is no change */
}

说是有这么多SF格式,实际上它只当场处理SF_G_sort和SF_U_o。其余的直接初始化完sf_node,加到sf_list(全局变量)链表中便结束了。
对SF_U_o,仍然是经典的列表处理,找到项目后,使用search_format_array来搜索对应处理项,如果没找到,或者有fs->vendor(只有U98是0,其余的都是>0的值)返回1。

const format_struct *search_format_array(const char *findme) {
    format_struct key;
    key.spec = findme;
    return bsearch(&key, format_array, format_array_count,
                   sizeof(format_struct), compare_format_structs
                  );
}


/* Note: upon conversion to the <pids> API the numerous former sort provisions
         for otherwise non-printable fields (pr_nop) have been retained. And,
         since the new library can sort on any item, many previously printable
         but unsortable fields have now been made sortable. */
/* there are about 211 listed */
/* Many of these are placeholders for unsupported options. */
static const format_struct format_array[] = { /*
 .spec        .head      .pr               .sr                   .width .vendor .flags  */
{"%cpu",      "%CPU",    pr_pcpu,          PIDS_extra,               4,    BSD,  ET|RIGHT}, /*pcpu*/
{"%mem",      "%MEM",    pr_pmem,          PIDS_VM_RSS,              4,    BSD,  PO|RIGHT}, /*pmem*/
{"_left",     "LLLLLLLL", pr_t_left,       PIDS_noop,                8,    TST,  ET|LEFT},
{"_left2",    "L2L2L2L2", pr_t_left2,      PIDS_noop,                8,    TST,  ET|LEFT},
{"_right",    "RRRRRRRRRRR", pr_t_right,   PIDS_noop,                11,   TST,  ET|RIGHT},

回到parse_bsd_option中。其余的选项大多数是在设置标记位,不再重复了。

    case 'X':
        trace("X old Linux i386 register format\n");
        format_flags |= FF_LX;
        break;
    case 'Z':  /* FreeBSD does MAC like SGI's Irix does it */
        trace("Z print security label for Mandatory Access Control.\n");
        format_modifiers |= FM_M;
        break;
    case 'a':
        trace("a select all w/tty, including other users\n");
        simple_select |= SS_B_a;
        break;
    case 'c':
        trace("c true command name\n");
        bsd_c_option = 1;
        break;

目前只剩下来最后一个,parse_sysv_option(),想必也不会有什么惊喜。

/***************** parse SysV options, including Unix98  *****************/
static const char *parse_sysv_option(void) {
    const char *arg;
    const char *err;

    flagptr = ps_argv[thisarg];
    while(*++flagptr) {
        switch(*flagptr) {
        case 'A':
            trace("-A selects all processes\n");
            all_processes = 1;
            break;
        case 'C': /* end */
            trace("-C select by process name\n");  /* Why only HP/UX and us? */
            arg=get_opt_arg();
            if(!arg) return _("list of command names must follow -C");
            err=parse_list(arg, parse_cmd);
            if(err) return err;
            selection_list->typecode = SEL_COMM;
            return NULL; /* can't have any more options */
        case 'F':  /* DYNIX/ptx -f plus sz,rss,psr=ENG between c and stime */
            trace("-F does fuller listing\n");
            format_modifiers |= FM_F;
            format_flags |= FF_Uf;
            unix_f_option = 1; /* does this matter? */
            break;

事实也确实如此,这里都是我们看过的函数,不再过多介绍了。
终于看完了parse_all_options的所有内容,回到上一层arg_parse。

int arg_parse(int argc, char *argv[]) {
    const char *err = NULL;
    const char *err2 = NULL;
    ps_argc = argc;
    ps_argv = argv;
    thisarg = 0;

    if(personality & PER_FORCE_BSD) goto try_bsd;

    err = parse_all_options();  //<----------
    if(err) goto try_bsd;
    err = thread_option_check();
    if(err) goto try_bsd;
    err = process_sf_options();
    if(err) goto try_bsd;
    err = select_bits_setup();
    if(err) goto try_bsd;

看来我们只走了一小步,thread_option_check全是在处理thread_flags这个全局变量,根据之前parse_all_options传入的内容对其进行设置。process_sf_options对参数“o”进行处理,代码比较复杂,我们单独拖出来看一看。

首先其注释表示这个功能是遗留下来的坑,前人挖坑埋后人系列。

/**************************************************************************
 * Used to parse option O lists. Option O is shared between
 * sorting and formatting. Users may expect one or the other.
 * The "broken" flag enables a really bad Unix98 misfeature.
 */
const char *process_sf_options(void) {
    sf_node *sf_walk;

    if(sf_list) {
        const char *err;
        err = parse_O_option(sf_list);
        if(err) return err;
    }

    if(format_list) catastrophic_failure(__FILE__, __LINE__, _("bug: must reset the list first"));

第一阶段的代码调用parse_O_option,这玩意儿也是个中型函数。没办法,看一看它是什么。

/*
 * Used to parse option O lists. Option O is shared between
 * sorting and formatting. Users may expect one or the other.
 * Recursion is to preserve original order.
 */
static const char *parse_O_option(sf_node *sfn) {
    const char *err;     /* error code that could or did happen */

    if(sfn->next) {
        err = parse_O_option(sfn->next);
        if(err) return err;
    }

    switch(sfn->sf_code) {
    case SF_B_o:
    case SF_G_format:
    case SF_U_o: /*** format ***/
        err = format_parse(sfn);
        if(!err) already_parsed_format = 1;
        break;
    case SF_U_O:                                /*** format ***/
        /* Can have -l -f f u... set already_parsed_format like DEC does */
        if(already_parsed_format) return _("option -O can not follow other format options");
        err = format_parse(sfn);
        if(err) return err;
        already_parsed_format = 1;
        O_wrap(sfn,'u'); /* must wrap user format in default */
        break;
    case SF_B_O:                                /***  both  ***/
        if(have_gnu_sort || already_parsed_sort) err = _("multiple sort options");
        else err = verify_short_sort(sfn->sf);
        if(!err) { /* success as sorting code */
            short_sort_parse(sfn);
            already_parsed_sort = 1;
            return NULL;
        }
        if(already_parsed_format) {
            err = _("option O is neither first format nor sort order");
            break;
        }
        if(!format_parse(sfn)) { /* if success as format code */
            already_parsed_format = 1;
            O_wrap(sfn,'b'); /* must wrap user format in default */
            return NULL;
        }
        break;
    case SF_G_sort:
    case SF_B_m:                 /***  sort  ***/
        if(already_parsed_sort) err = _("multiple sort options");
        else err = long_sort_parse(sfn);
        already_parsed_sort = 1;
        break;
    default:                                    /***  junk  ***/
        catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
    }
    return err; /* could be NULL */
}

很不妙,一上来就是另一个parser。对G_o、G_format、U_o三个情况而言,进入format_parse。

    case SF_B_o:
    case SF_G_format:
    case SF_U_o: /*** format ***/
        err = format_parse(sfn);
        if(!err) already_parsed_format = 1;

format_parse定义如下,由一个大型状态机构成。前面的状态机只是检查是否符合语法,并不做其他事情。通过后,开始处理。

/******************************************************************
 * Used to parse option AIX field descriptors.
 * Put each completed format_node onto the list starting at ->f_cooked
 */
static const char *aix_format_parse(sf_node *sfn) {
    char *buf;                   /* temp copy of arg to hack on */
    char *walk;
    int items;

    /*** sanity check and count items ***/
    items = 0;
    walk = sfn->sf;
    /* state machine */ {
        int c;
initial:
        c = *walk++;
        if(c=='%')    goto get_desc;
        if(!c)        goto looks_ok;
        /* get_text: */
        items++;
get_more_text:
        c = *walk++;
        if(c=='%')    goto get_desc;
        if(c)         goto get_more_text;
        goto looks_ok;
get_desc:
        items++;
        c = *walk++;
        if(c)         goto initial;
        return _("improper AIX field descriptor");
looks_ok:
        ;
    }

处理阶段,复制一份sfn->sf,这是带%的命令行。只要不是%%,就交给search_aix_array去搜索对应的列。

    /*** sanity check passed ***/
    buf = strdup(sfn->sf);
    walk = sfn->sf;

    while(items--) {
        format_node *fnode;  /* newly allocated */
        format_node *endp;   /* for list manipulation */

        if(*walk == '%') {
            const aix_struct *aix;
            walk++;
            if(*walk == '%') goto double_percent;
            aix = search_aix_array(*walk);

search_aix_array的定义如下:

const aix_struct *search_aix_array(const int findme) {
    const aix_struct *walk = aix_array;
    while(walk->desc != '~') {
        if(walk->desc == findme) return walk;
        walk++;
    }
    return NULL;
}

它搜索的是这样一个数组。

/*************************** AIX formats ********************/
/* Convert AIX format codes to normal format specifiers. */
static const aix_struct aix_array[] = {
    {'C', "pcpu",   "%CPU"},
    {'G', "group",  "GROUP"},
    {'P', "ppid",   "PPID"},
    {'U', "user",   "USER"},
    {'a', "args",   "COMMAND"},
    {'c', "comm",   "COMMAND"},
    {'g', "rgroup", "RGROUP"},
    {'n', "nice",   "NI"},
    {'p', "pid",    "PID"},
    {'r', "pgid",   "PGID"},
    {'t', "etime",  "ELAPSED"},
    {'u', "ruser",  "RUSER"},
    {'x', "time",   "TIME"},
    {'y', "tty",    "TTY"},
    {'z', "vsz",    "VSZ"},
    {'~', "~",      "~"} /* NULL would ruin alphabetical order */
};

回到之前的函数。如果找到了,则调用do_one_spec来处理对应的规范和表头。

            walk++;
            if(!aix) {
                free(buf);
                return _("unknown AIX field descriptor");
            }
            fnode =  do_one_spec(aix->spec, aix->head);

do_one_spec也是个大函数,定义如下:

/****************  Parse single format specifier *******************/
static format_node *do_one_spec(const char *spec, const char *override) {
    const format_struct *fs;
    const macro_struct *ms;

    fs = search_format_array(spec);

函数一上来就在format_array中找对应的spec(参数1)。之前已经见识过这个format_array了:

static const format_struct format_array[] = { /*
 .spec        .head      .pr               .sr                   .width .vendor .flags  */
{"%cpu",      "%CPU",    pr_pcpu,          PIDS_extra,               4,    BSD,  ET|RIGHT}, /*pcpu*/
{"%mem",      "%MEM",    pr_pmem,          PIDS_VM_RSS,              4,    BSD,  PO|RIGHT}, /*pmem*/

如果找到,则对其进行处理,生成format_node并返回。

    if(fs) {
        int w1, w2;
        format_node *thisnode;
        thisnode = xmalloc(sizeof(format_node));
        if(fs->flags & CF_PIDMAX) {
            w1 = (int)procps_pid_length();
            w2 = strlen(fs->head);
            if(w2>w1) w1=w2; // FIXME w/ separate header/body column sizing
        } else {
            w1 = fs->width;
        }
        if(override) {
            w2 = strlen(override);
            thisnode->width = (w1>w2)?w1:w2;
            thisnode->name = strdup(override);
        } else {
            thisnode->width = w1;
            thisnode->name = strdup(fs->head);
        }
        thisnode->pr = fs->pr;
        thisnode->vendor = fs->vendor;
        thisnode->flags = fs->flags;
        thisnode->next = NULL;
        return thisnode;
    }

format_node的各项解释如下:
1) .pr ,处理函数,处理函数由format_array定义,各项形如:

/* normal %CPU in ##.# format. */
static int pr_pcpu(char *restrict const outbuf, const proc_t *restrict const pp) {
    unsigned long long total_time;   /* jiffies used by this process */
    unsigned pcpu;                   /* scaled %cpu, 999 means 99.9% */
    unsigned long long seconds;      /* seconds of process life */
    setREL3(TICS_ALL,TICS_ALL_C,TIME_ELAPSED)
    pcpu = 0;
    if(include_dead_children) total_time = rSv(TICS_ALL_C, ull_int, pp);
    else total_time = rSv(TICS_ALL, ull_int, pp);
    seconds = rSv(TIME_ELAPSED, ull_int, pp);
    if(seconds) pcpu = (total_time * 1000ULL / Hertz) / seconds;
    if (pcpu > 999U)
        return snprintf(outbuf, COLWID, "%u", pcpu/10U);
    return snprintf(outbuf, COLWID, "%u.%u", pcpu/10U, pcpu%10U);
}

2) .vendor ,哪个系统引入的功能。
3) .flags,预设的flag。
4) .next,与其关联的下一个节点(链表)。

如果没有找到format_array,则尝试按macro再次查找。macro array是一组对应的字符映射关系,很像C的宏:

static const macro_struct macro_array[] = {
    {"DFMT",     "pid,tname,state,cputime,cmd"},         /* Digital's default */
    {"DefBSD",   "pid,tname,stat,bsdtime,args"},               /* Our BSD default */
    {"DefSysV",  "pid,tname,time,cmd"},                     /* Our SysV default */

将macro展开后,对macro中每个section,调用自己再解析一次。

    /* That failed, so try it as a macro. */
    ms = search_macro_array(spec);
    if(ms) {
        format_node *list = NULL;
        format_node *newnode;
        const char *walk;
        int dist;
        char buf[16]; /* trust strings will be short (from above, not user) */
        walk = ms->head;
        while(*walk) {
            dist = strcspn(walk, ", ");
            strncpy(buf,walk,dist);
            buf[dist] = '\0';
            newnode = do_one_spec(buf,override); /* call self, assume success */
            newnode->next = list;
            list = newnode;
            walk += dist;
            if(*walk) walk++;
        }
        return list;
    }
    return NULL;   /* bad, spec not found */
}

继续回到上层,把%之前的内容dump出来,保存在fnode中。检查最后一个节点,保存到sfn->f_cooked中,然后退出。

            if(!fnode) {
                free(buf);
                return _("AIX field descriptor processing bug");
            }
        } else {
            size_t len;
            len = strcspn(walk, "%");
            memcpy(buf,walk,len);
            if(0) {
double_percent:
                len = 1;
                buf[0] = '%';
            }
            buf[len] = '\0';
            walk += len;
            fnode = xmalloc(sizeof(format_node));
            fnode->width = len < INT_MAX ? len : INT_MAX;
            fnode->name = strdup(buf);
            fnode->pr = NULL;     /* checked for */
            fnode->vendor = AIX;
            fnode->flags = CF_PRINT_EVERY_TIME;
            fnode->next = NULL;
        }

        endp = fnode;
        while(endp->next) endp = endp->next;  /* find end */
        endp->next = sfn->f_cooked;
        sfn->f_cooked = fnode;
    }
    free(buf);
    already_parsed_format = 1;
    return NULL;
}

再回到最外面的那层。后面就比较简单了,分别维护两个链表,一个是format_list,一个是sort_list,将二者分类放到不同的链表中。

    /* merge formatting info of sf_list into format_list here */
    sf_walk = sf_list;
    while(sf_walk) {
        format_node *fmt_walk;
        fmt_walk = sf_walk->f_cooked;
        sf_walk->f_cooked = NULL;
        while(fmt_walk) {  /* put any nodes onto format_list in opposite way */
            format_node *travler;
            travler = fmt_walk;
            fmt_walk = fmt_walk->next;
            travler->next = format_list;
            format_list = travler;
        }
        sf_walk = sf_walk->next;
    }

    /* merge sorting info of sf_list into sort_list here */
    sf_walk = sf_list;
    while(sf_walk) {
        sort_node *srt_walk;
        srt_walk = sf_walk->s_cooked;
        sf_walk->s_cooked = NULL;
        if (srt_walk) {
            sort_node *travler = srt_walk;
            while (travler->next) travler = travler->next;
            travler->next = sort_list;
            sort_list = srt_walk;
        }
        sf_walk = sf_walk->next;
    }

并在接下来处理PS_FORMAT环境变量(format_parse),然后重复放到format_list的步骤。

// Get somebody to explain how -L/-T is supposed to interact
// with sorting. Do the threads remain grouped, with sorting
// by process, or do the threads get sorted by themselves?
if(sort_list && (thread_flags&TF_no_sort)) {
    return _("tell <procps@freelists.org> what you expected");
}

// If nothing else, try to use $PS_FORMAT before the default.
if(!format_flags && !format_modifiers && !format_list) {
    char *tmp;
    tmp = getenv("PS_FORMAT");  /* user override kills default */
    if(tmp && *tmp) {
        const char *err;
        sf_node sfn;
        if(thread_flags&TF_must_use) return _("tell <procps@freelists.org> what you want (-L/-T, -m/m/H, and $PS_FORMAT)");
        sfn.sf = tmp;
        sfn.f_cooked = NULL;
        err = format_parse(&sfn);
        if(!err) {
            format_node *fmt_walk;
            fmt_walk = sfn.f_cooked;
            while(fmt_walk) {  /* put any nodes onto format_list in opposite way */
                format_node *travler;
                travler = fmt_walk;
                fmt_walk = fmt_walk->next;
                travler->next = format_list;
                format_list = travler;
            }
            return NULL;
        }
        // FIXME: prove that this won't be hit on valid bogus-BSD options
        fprintf(stderr, _("warning: $PS_FORMAT ignored. (%s)\n"), err);
    }
}

如果有指定format_flags,则同样处理它。

if(format_list) {
    if(format_flags) return _("conflicting format options");
    if(format_modifiers) return _("can not use output modifiers with user-defined output");
    if(thread_flags&TF_must_use) return _("-L/-T with H/m/-m and -o/-O/o/O is nonsense");
    return NULL;
}

do {
    const char *spec;
    switch(format_flags) {

    default:
        return _("conflicting format options");

    /* These can be NULL, which enables SysV list generation code. */
    case 0:
        spec=NULL;
        break;
           ……
    case FF_Lm:
        spec="OL_m";
        break;

    /* This is the sole FLASK security option. */
    case FF_Fc:
        spec="FLASK_context";
        break;

    }  /* end switch(format_flags) */

    // not just for case 0, since sysv_l_format and such may be NULL
    if(!spec) return generate_sysv_list();

    do {
        format_node *fmt_walk;
        fmt_walk = do_one_spec(spec, NULL); /* use override "" for no headers */
        while(fmt_walk) {  /* put any nodes onto format_list in opposite way */
            format_node *travler;
            travler = fmt_walk;
            fmt_walk = fmt_walk->next;
            travler->next = format_list;
            format_list = travler;
        }
    } while(0);
} while(0);

接下来,对format_modifiers进行处理。fmt_add_after、fmt_delete将字符串与format_list的项目name属性做对比,并添加项目/删除项目。

    do {
        format_node *fn;
        if(format_modifiers & FM_j) {
            fn = do_one_spec("pgid", NULL);
            if(!fmt_add_after("PPID", fn)) if(!fmt_add_after("PID", fn))
                    catastrophic_failure(__FILE__, __LINE__, _("internal error: no PID or PPID for -j option"));
            fn = do_one_spec("sid", NULL);
            if(!fmt_add_after("PGID", fn)) return _("lost my PGID");
        }
        if(format_modifiers & FM_y) {
            /* TODO: check for failure to do something, and complain if so */
            fmt_delete("F");
            fn = do_one_spec("rss", NULL);
            if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
        }
        if(format_modifiers & FM_c) {
            fmt_delete("%CPU");
            fmt_delete("CPU");
            fmt_delete("CP");
            fmt_delete("C");
            fmt_delete("NI");
            fn = do_one_spec("class", NULL);
            if(!fmt_add_after("PRI", fn))
                catastrophic_failure(__FILE__, __LINE__, _("internal error: no PRI for -c option"));
            fmt_delete("PRI"); /* we want a different one */
            fn = do_one_spec("pri", NULL);
            if(!fmt_add_after("CLS", fn)) return _("lost my CLS");
        }
        if(thread_flags & TF_U_T) {
            fn = do_one_spec("spid", NULL);
            if(!fmt_add_after("PID", fn) && (thread_flags&TF_must_use))
                return _("-T with H/-m/m but no PID for SPID to follow");
        }
        if(thread_flags & TF_U_L) {
            fn = do_one_spec("lwp", NULL);
            if(fmt_add_after("SID",  fn)) goto did_lwp;
            if(fmt_add_after("SESS", fn)) goto did_lwp;
            if(fmt_add_after("PGID", fn)) goto did_lwp;
            if(fmt_add_after("PGRP", fn)) goto did_lwp;
            if(fmt_add_after("PPID", fn)) goto did_lwp;
            if(fmt_add_after("PID",  fn)) goto did_lwp;
            if(thread_flags&TF_must_use)
                return _("-L with H/-m/m but no PID/PGID/SID/SESS for NLWP to follow");
did_lwp:
            fn = do_one_spec("nlwp", NULL);
            fmt_add_after("%CPU",  fn);
        }
        if(format_modifiers & FM_M) {   // Mandatory Access Control, IRIX style
            fn = do_one_spec("label", NULL);
            fn->next=format_list;
            format_list=fn;
        }
        /* Do personality-specific translations not covered by format_flags.
         * Generally, these only get hit when personality overrides unix output.
         * That (mostly?) means the Digital and Debian personalities.
         */
        if((personality & PER_ZAP_ADDR) && (format_flags & FF_Ul)) {
            fn = do_one_spec("sgi_p", NULL);
            if(fmt_add_after("ADDR", fn)) fmt_delete("ADDR");
        }
        if((personality & PER_SANE_USER) && (format_flags & FF_Uf)) {
            fn = do_one_spec("user", NULL);
            if(fmt_add_after("UID", fn)) fmt_delete("UID");
        }
    } while(0);

    return NULL;
}

终于回到最开始的arg_parse。

err = parse_all_options();
if(err) goto try_bsd;
err = thread_option_check();
if(err) goto try_bsd;
err = process_sf_options(); //<------
if(err) goto try_bsd;
err = select_bits_setup(); 
if(err) goto try_bsd;

我们接下来是select_bits_setup。这是一个设置select_bits全局变量的函数,不介绍了,全是魔法数字,后面碰着再看。这个函数调用完成后,arg_parse也结束了。回到最初的起点main()。

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");

init_output(); /* must be between parser and output */

接下来的arg_check_conflicts没有什么惊喜,只是检查有没有冲突的参数。进入init_output。

void init_output(void)
{
    int outbuf_pages;
    char *outbuf;

    // add page_size-1 to round up
    outbuf_pages = (OUTBUF_SIZE+SPACE_AMOUNT+page_size-1)/page_size;
    outbuf = mmap(
                 0,
                 page_size * (outbuf_pages+1), // 1 more, for guard page at high addresses
                 PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS,
                 -1,
                 0);

    if(outbuf == MAP_FAILED)
        catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));

    memset(outbuf, ' ', SPACE_AMOUNT);
    if(SPACE_AMOUNT==page_size)
        mprotect(outbuf, page_size, PROT_READ);
    mprotect(outbuf + page_size*outbuf_pages, page_size, PROT_NONE); // guard page
    saved_outbuf = outbuf + SPACE_AMOUNT;
    // available space:  page_size*outbuf_pages-SPACE_AMOUNT
    seconds_since_1970 = time(NULL);

    check_header_width();
}

OUTBUF_SIZE是2 * 64 * 1024, SPACE_AMOUNT是144,page_size是分页大小,一般认为是4096。所以outbuf实际上是由33页构成(如果page_size=4096)。mmap申请34页(139,264字节)。前SPACE_AMOUNT字节设置为空格,最后一页无权限。

回到main中,还剩最后一点点代码:

    lists_and_needs(); //<===
    finalize_stacks();

    if(forest_type || sort_list) fancy_spew(); /* sort or forest */
    else simple_spew(); /* no sort, no forest */
    show_one_proc((proc_t *)-1,format_list); /* no output yet? */

    procps_pids_unref(&Pids_info);
    return 0;
}

继续看lists_and_needs。check_headers用于检查有多少个header(遍历format_list中有name的、有pr项的并计数)。然后后面的代码用于对列表中的项目需求进行处理,并修改一部分类型节点的pr值。

/***** munge lists and determine final needs */
static void lists_and_needs(void) {
    check_headers();

    // only care about the difference when showing both
    if(thread_flags & TF_show_both) {
        format_node pfn, tfn; // junk, to handle special case at begin of list
        format_node *walk = format_list;
        format_node *p_end = &pfn;
        format_node *t_end = &tfn;
        while(walk) {
            format_node *new = xmalloc(sizeof(format_node));
            memcpy(new,walk,sizeof(format_node));
            p_end->next = walk;
            t_end->next = new;
            p_end       = walk;
            t_end       = new;
            switch(walk->flags & CF_PRINT_MASK) {
            case CF_PRINT_THREAD_ONLY:
                p_end->pr   = pr_nop;
                break;
            case CF_PRINT_PROCESS_ONLY:
                t_end->pr   = pr_nop;
                break;
            default:
                catastrophic_failure(__FILE__, __LINE__, _("please report this bug"));
            // FALL THROUGH
            case CF_PRINT_AS_NEEDED:
            case CF_PRINT_EVERY_TIME:
                break;
            }
            walk = walk->next;
        }
        t_end->next = NULL;
        p_end->next = NULL;
        proc_format_list = pfn.next;
        task_format_list = tfn.next;
    } else {
        proc_format_list = format_list;
        task_format_list = format_list;
    }
}

回到main,阅读finalize_stacks()。

psproc源码阅读 - 2

main中接下来的函数都比较重要,所以这里就分段来介绍了。

arg_parse(argc,argv);

/* check for invalid combination of arguments */
arg_check_conflicts();

首先是arg_parse。

int arg_parse(int argc, char *argv[]) {
    const char *err = NULL;
    const char *err2 = NULL;
    ps_argc = argc;
    ps_argv = argv;
    thisarg = 0;

    if(personality & PER_FORCE_BSD) goto try_bsd;

    err = parse_all_options();
    if(err) goto try_bsd;
    err = thread_option_check();
    if(err) goto try_bsd;
    err = process_sf_options();
    if(err) goto try_bsd;
    err = select_bits_setup();
    if(err) goto try_bsd;

    choose_dimensions();
    return 0;

try_bsd:
    trace("--------- now try BSD ------\n");

    reset_global();
    reset_parser();
    reset_sortformat();
    format_flags = 0;
    ps_argc = argc;
    ps_argv = argv;
    thisarg = 0;
    /* no need to reset flagptr */
    force_bsd=1;
    prefer_bsd_defaults=1;
    if(!( (PER_OLD_m|PER_BSD_m) & personality )) /* if default m setting... */
        personality |= PER_OLD_m; /* Prefer old Linux over true BSD. */
    /* Do not set PER_FORCE_BSD! It is tested below. */

    err2 = parse_all_options();
    if(err2) goto total_failure;
    err2 = thread_option_check();
    if(err2) goto total_failure;
    err2 = process_sf_options();
    if(err2) goto total_failure;
    err2 = select_bits_setup();
    if(err2) goto total_failure;

    choose_dimensions();
    return 0;

total_failure:
    reset_parser();
    if(personality & PER_FORCE_BSD) fprintf(stderr, _("error: %s\n"), err2);
    else fprintf(stderr, _("error: %s\n"), err);
    do_help(NULL, EXIT_FAILURE);
}

先看第一部分,全局变量personality由set_personality设置,大体就是根据不同的操作系统和架构,来设置不同的参数。

int arg_parse(int argc, char *argv[]) {
    const char *err = NULL;
    const char *err2 = NULL;
    ps_argc = argc;
    ps_argv = argv;
    thisarg = 0;

    if(personality & PER_FORCE_BSD) goto try_bsd;

对BSD而言,其personality是包含PER_FORCE_BSD位的,但是对linux则没有。因此如果有这个位,则优先尝试bsd。

case_bsd:
    personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m;
    prefer_bsd_defaults = 1;
    bsd_j_format = "FB_j";
    bsd_l_format = "FB_l";
    /* bsd_s_format not used */
    bsd_u_format = "FB_u";
    bsd_v_format = "FB_v";
    return NULL;

否则继续执行parse_all_options()。

err = parse_all_options();

开始阅读parse_all_options函数,对每个当前的参数,调用arg_type(ps_argv[thisarg])获取其类型。

/* First assume sysv, because that is the POSIX and Unix98 standard. */
static const char *parse_all_options(void) {
    const char *err = NULL;
    int at;
    while(++thisarg < ps_argc) {
        trace("parse_all_options calling arg_type for \"%s\"\n", ps_argv[thisarg]);
        at = arg_type(ps_argv[thisarg]);
        trace("ps_argv[thisarg] is %s\n", ps_argv[thisarg]);
        switch(at) {
        case ARG_GNU:
            err = parse_gnu_option();
            break;
        case ARG_SYSV:
            if(!force_bsd) {  /* else go past case ARG_BSD */
                err = parse_sysv_option();
                break;

                case ARG_BSD:
                    if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad");
            }
            prefer_bsd_defaults = 1;
            err = parse_bsd_option();
            break;
        case ARG_PGRP:
        case ARG_SESS:
        case ARG_PID:
            prefer_bsd_defaults = 1;
            err = parse_trailing_pids();
            break;
        case ARG_END:
        case ARG_FAIL:
            trace("              FAIL/END on [%s]\n",ps_argv[thisarg]);
            return _("garbage option");
            break;
        default:
            printf("                  ?    %s\n",ps_argv[thisarg]);
            return _("something broke");
        } /* switch */
        if(err) return err;
    } /* while */
    return NULL;
}

arg_type的定义如下,即:如果是字母开头,则认为是BSD风格的参数。如果是数字开头,则认为是PID,如果是+开头的,则认为是ARG_SESS类型。如果是其他情况且非-开头,认为是非法符号。然后再看下一个字符,如果是字母开头的,认为是SYSV参数(例如-a),如果是数字开头的,则认为是PGRP,如果其他字符且不是-,认为非法。如果是--开头的,再看第三个字符,是不是字母,如果是的话则认为是GNU参数。

static int arg_type(const char *str) {
    int tmp = str[0];
    if((tmp>='a') && (tmp<='z'))   return ARG_BSD;
    if((tmp>='A') && (tmp<='Z'))   return ARG_BSD;
    if((tmp>='0') && (tmp<='9'))   return ARG_PID;
    if(tmp=='+')                   return ARG_SESS;
    if(tmp!='-')                   return ARG_FAIL;
    tmp = str[1];
    if((tmp>='a') && (tmp<='z'))   return ARG_SYSV;
    if((tmp>='A') && (tmp<='Z'))   return ARG_SYSV;
    if((tmp>='0') && (tmp<='9'))   return ARG_PGRP;
    if(tmp!='-')                   return ARG_FAIL;
    tmp = str[2];
    if((tmp>='a') && (tmp<='z'))   return ARG_GNU;
    if((tmp>='A') && (tmp<='Z'))   return ARG_GNU;
    if(tmp=='\0')                  return ARG_END;
    return ARG_FAIL;
}

对比ps的man,即可理解:

DESCRIPTION
       ps displays information about a selection of the active processes.  If you want a
       repetitive update of the selection and the displayed information, use top(1) instead.

       This version of ps accepts several kinds of options:

       1   UNIX options, which may be grouped and must be preceded by a dash.
       2   BSD options, which may be grouped and must not be used with a dash.
       3   GNU long options, which are preceded by two dashes.

总之为了保证兼容性,ps的命令行非常混乱。

加号和数字的作用:

   --sort spec
          Specify sorting order.  Sorting syntax is [+|-]key[,[+|-]key[,...]].  Choose a
          multi-letter key from the STANDARD FORMAT SPECIFIERS section.  The "+" is
          optional since default direction is increasing numerical or lexicographic
          order.  Identical to k.  For example: ps jax --sort=uid,-ppid,+pid

PROCESS SELECTION BY LIST
       These options accept a single argument in the form of a blank-separated or
       comma-separated list.  They can be used multiple times.  For example:
       ps -p "1 2" -p 3,4

       -123   Identical to --pid 123.

       123    Identical to --pid 123.

在所有类型中,ARG_END、ARG_FAIL、default会导致直接退出。

    case ARG_PGRP:
    case ARG_SESS:
    case ARG_PID:
        prefer_bsd_defaults = 1;
        err = parse_trailing_pids();
        break;

PGRP、SESS、PID会使ps进一步解析后面的pid。

/*************** process trailing PIDs  **********************/
static const char *parse_trailing_pids(void) {
    selection_node *pidnode;  /* pid */
    selection_node *grpnode;  /* process group */
    selection_node *sidnode;  /* session */
    char **argp;     /* pointer to pointer to text of PID */
    const char *err;       /* error code that could or did happen */
    int i;

    i = ps_argc - thisarg;  /* how many trailing PIDs, SIDs, PGRPs?? */
    argp = ps_argv + thisarg;
    thisarg = ps_argc - 1;   /* we must be at the end now */

    pidnode = xmalloc(sizeof(selection_node));
    pidnode->u = xmalloc(i*sizeof(sel_union)); /* waste is insignificant */
    pidnode->n = 0;

    grpnode = xmalloc(sizeof(selection_node));
    grpnode->u = xmalloc(i*sizeof(sel_union)); /* waste is insignificant */
    grpnode->n = 0;

    sidnode = xmalloc(sizeof(selection_node));
    sidnode->u = xmalloc(i*sizeof(sel_union)); /* waste is insignificant */
    sidnode->n = 0;

    while(i--) {
        char *data;
        data = *(argp++);
        switch(*data) {
        default:
            err = parse_pid(  data, pidnode->u + pidnode->n++);
            break;
        case '-':
            err = parse_pid(++data, grpnode->u + grpnode->n++);
            break;
        case '+':
            err = parse_pid(++data, sidnode->u + sidnode->n++);
            break;
        }
        if(err) return err;     /* the node gets freed with the list */
    }

    if(pidnode->n) {
        pidnode->next = selection_list;
        selection_list = pidnode;
        selection_list->typecode = SEL_PID;
    }  /* else free both parts */

    if(grpnode->n) {
        grpnode->next = selection_list;
        selection_list = grpnode;
        selection_list->typecode = SEL_PGRP;
    }  /* else free both parts */

    if(sidnode->n) {
        sidnode->next = selection_list;
        selection_list = sidnode;
        selection_list->typecode = SEL_SESS;
    }  /* else free both parts */

    return NULL;
}

解析时要求它是一个1~0x7fffffff的正整数。并按+、-、默认的情况分别放置在sid/gid/pidnode中。

static const char *parse_pid(char *str, sel_union *ret) {
    char *endp;
    unsigned long num;
    num = strtoul(str, &endp, 0);
    if(*endp != '\0')      return _("process ID list syntax error");
    if(num<1)              return _("process ID out of range");
    if(num > 0x7fffffffUL) return _("process ID out of range");
    ret->pid = num;
    return 0;
}

对ARG_GNU而言,处理函数是parse_gnu_option

parser.c:parse_all_options

    case ARG_GNU:
        err = parse_gnu_option();
        break;

parse_gnu_option的开头列出了一组支持的参数。

static const gnu_table_struct gnu_table[] = {
    {"Group",         &&case_Group},       /* rgid */
    {"User",          &&case_User},        /* ruid */
    {"cols",          &&case_cols},
    {"columns",       &&case_columns},
    {"context",       &&case_context},
    {"cumulative",    &&case_cumulative},
    {"deselect",      &&case_deselect},    /* -N */
    {"forest",        &&case_forest},      /* f -H */

这里的case_Group之类的不是什么全局变量,而是本地标签,第一次看到能这么用,很神奇……

        {"version",       &&case_version},
        {"width",         &&case_width},
    };
    const int gnu_table_count = sizeof(gnu_table)/sizeof(gnu_table_struct);

    s = ps_argv[thisarg]+2;
    sl = strcspn(s,":=");
    if(sl > 15) return _("unknown gnu long option");
    strncpy(buf, s, sl);
    buf[sl] = '\0';
    flagptr = s+sl;

    found = bsearch(&findme, gnu_table, gnu_table_count,
                    sizeof(gnu_table_struct), compare_gnu_table_structs
                   );

    if(!found) {
        if (!strcmp(buf, the_word_help))
            goto case_help;
        return _("unknown gnu long option");
    }

    goto *(found->jump);    /* See gcc extension info.  :-)   */

case_Group:
    trace("--Group\n");
    arg = grab_gnu_arg();
    if(!arg) return _("list of real groups must follow --Group");
    err=parse_list(arg, parse_gid);
    if(err) return err;
    selection_list->typecode = SEL_RGID;
    return NULL;
case_User:
    trace("--User\n");
    arg = grab_gnu_arg();
    if(!arg) return _("list of real users must follow --User");
    err=parse_list(arg, parse_uid);
    if(err) return err;
    selection_list->typecode = SEL_RUID;
    return NULL;

先逐行读一下代码。s是argv[i] + 2,这是因为ARG_GNU是“--”开头的,跳过前两个字符。sl是:=前的字符数。然后将:=前的内容拷贝到buf中。buf定义为buf[16]所以限制长度不能大于15。在这之后,flagptr就是:=开始的字符。

然后,通过bsearch库函数在gnu_table中搜索findme={buf, NULL}。如果找到就直接跳到对应标签上,这语法也是很离谱。

先看看man手册中对这些参数的定义:

   --cols n
          Set screen width.

   --columns n
          Set screen width.

   --cumulative
          Include some dead child process data (as a sum with the parent).

有但不是全有,比如--Group就不在主词条里面(但在其他词条的描述里有提到)。挑几个比较有特点的读一下好了。

首先是它们的一个通用工具函数grab_gnu_arg,它在所有需要额外参数的,比如--cols n中被使用。

/*
 * Return the argument or NULL
 */
static const char *grab_gnu_arg(void) {
    switch(*flagptr) {    /* argument is part of ps_argv[thisarg] */
    default:
        return NULL;                     /* something bad */
    case '=':
    case ':':
        if(*++flagptr) return flagptr;   /* found it */
        return NULL;                     /* empty '=' or ':' */
    case '\0': /* try next argv[] */
        ;
    }
    if(thisarg+2 > ps_argc) return NULL;   /* there is nothing left */
    /* argument follows ps_argv[thisarg] */
    if(*(ps_argv[thisarg+1]) == '\0') return NULL;
    return ps_argv[++thisarg];
}
//
//<---->
//
case_cols:
case_width:
case_columns:
    trace("--cols\n");
    arg = grab_gnu_arg();
    if(arg && *arg) {
        long t;
        char *endptr;
        t = strtol(arg, &endptr, 0);
        if(!*endptr && (t>0) && (t<2000000000)) {
            screen_cols = (int)t;
            return NULL;
        }
    }
    return _("number of columns must follow --cols, --width, or --columns");

如果指定的是例如--cols=2,--cols:2,则返回=和:之后的部分。如果当前命令已经到头(\0),则看看下一个命令是不是有效的(非"\0"),如果是,返回,thisarg+1。

以--cols为例,这里设置screen_cols为“0~2000000000”中间的一个整数(用0x7ffffff不好吗……)。

再挑一个典型。

case_Group:
    trace("--Group\n");
    arg = grab_gnu_arg();
    if(!arg) return _("list of real groups must follow --Group");
    err=parse_list(arg, parse_gid);
    if(err) return err;
    selection_list->typecode = SEL_RGID;
    return NULL;

--Group这样后面跟一个list的,还需要parse_list来处理列表。

/*
 * Used to parse lists in a generic way. (function pointers)
 */
static const char *parse_list(const char *arg, const char *(*parse_fn)(char *, sel_union *) ) {
    selection_node *node;
    char *buf;                      /* temp copy of arg to hack on */
    char *sep_loc;                  /* separator location: " \t," */
    char *walk;
    int items;
    int need_item;
    const char *err;       /* error code that could or did happen */
    /*** prepare to operate ***/
    node = xmalloc(sizeof(selection_node));
    node->u = xmalloc(strlen(arg)*sizeof(sel_union)); /* waste is insignificant */
    node->n = 0;
    buf = strdup(arg);
    /*** sanity check and count items ***/
    need_item = 1; /* true */
    items = 0;
    walk = buf;
    err = _("improper list");
    do {
        switch(*walk) {
        case ' ':
        case ',':
        case '\t':
        case '\0':
            if(need_item) goto parse_error;
            need_item=1;
            break;
        default:
            if(need_item) items++;
            need_item=0;
        }
    } while (*++walk);
    if(need_item) goto parse_error;
    node->n = items;
    /*** actually parse the list ***/
    walk = buf;
    while(items--) {
        sep_loc = strpbrk(walk," ,\t");
        if(sep_loc) *sep_loc = '\0';
        if(( err=(parse_fn)(walk, node->u+items) )) goto parse_error;
        walk = sep_loc + 1; /* point to next item, if any */
    }
    free(buf);
    node->next = selection_list;
    selection_list = node;
    return NULL;
parse_error:
    free(buf);
    free(node->u);
    free(node);
    return err;
}

逐字查找,如果是逗号或者空白,则标记need_item,它们后面必须跟其他字符,统计一共有多少段。
然后设置node->n为计算到的总数。从头开始扫描空白或逗号,并对扫出来的部分调用parse_fn。parse_fn是传入的参数之一,看一下典型的parse_fn,这个是"C"参数传入的parse_fn,它将向ret->cmd(即(node->u + items)->cmd内拷贝长度为sizeof ret->cmd的字符串)。

static const char *parse_cmd(char *str, sel_union *ret) {
    strncpy(ret->cmd, str, sizeof ret->cmd);  // strncpy pads to end
    ret->cmd[sizeof(ret->cmd)-1] = '\0';      // but let's be safe
    return 0;
}

关于sel_union->cmd,在common.h中有定义。sizeof ret->cmd必然也就是64了。在parse_list的开头注意有:“node->u = xmalloc(strlen(arg)sizeof(sel_union));”,arg就是列表的长度,因此这里会试图分配列表长度64字节的数组给node->u。

typedef union sel_union {
    pid_t pid;
    pid_t ppid;
    uid_t uid;
    gid_t gid;
    dev_t tty;
    char  cmd[64];  /* this is _not_ \0 terminated */
} sel_union;

typedef struct selection_node {
    struct selection_node *next;
    sel_union *u;  /* used if selection type has a list of values */
    int n;         /* used if selection type has a list of values */
    int typecode;
} selection_node;

这篇已经足够长了,而且已经看完了parse_gnu_option部分。下一篇看看剩余的两个 sysv_option 和 bsd_option。

    case ARG_SYSV:
        if(!force_bsd) {  /* else go past case ARG_BSD */
            err = parse_sysv_option();
            break;

            case ARG_BSD:
                if(force_bsd && !(personality & PER_FORCE_BSD)) return _("way bad");
        }
        prefer_bsd_defaults = 1;
        err = parse_bsd_option();
        break;

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