使用 waitpid 和 WNOHANG 区分进程状态

Differentiate processes states using waitpid and WNOHANG

在构建 shell 程序时,我遇到了识别进程状态的问题。我面临的问题的描述是我有一个子进程列表,我正在尝试使用 waitpidWNOHANG 来弄清楚它们的状态。我想区分 3 种状态:TERMINATEDRUNNINGSUSPENDED。 (如以下代码中所定义) 我希望将进程状态更改为以上三种之一,但是现在此函数使 运行 进程状态变为 terminated,并且此函数也无法识别挂起的进程。 我想知道我做错了什么以及应该如何编写函数updateProcessList来实现它?

#define TERMINATED  -1
#define RUNNING 1
#define SUSPENDED 0

typedef struct process{
    cmdLine* cmd;                     /* the parsed command line*/
    pid_t pid;                        /* the process id that is running the command*/
    int status;                       /* status of the process: RUNNING/SUSPENDED/TERMINATED */
    struct process *next;             /* next process in chain */
} process;

void updateProcessList(process **process_list) {
    process *p = *process_list;
    int code = 0, status = 0,pidd = 0;
    while (p) {
        pidd = p->pid;
        code = waitpid(pidd, &status, WNOHANG);
        if (code == -1) {            /* child terminated*/
            p->status = TERMINATED;
        } else if(WIFEXITED(status)){
            p->status = TERMINATED;
        }else if(WIFSTOPPED(status)){
            p->status = SUSPENDED;
        }
        p = p->next;
    }
}

来自 man 2 waitpid:

RETURN VALUE

    waitpid():  on  success, returns the process ID of the child whose state has changed;
    if WNOHANG was specified and one or more child(ren) specified by pid exist, but  have
    not yet changed state, then 0 is returned.  On error, -1 is returned.

您应该检查 0 的 return 值...并修复其余的检查。

code = waitpid(ppid, &status, WNOHANG | WUNTRACED | WCONTINUED);

if (code == -1) {
    // Handle error somehow... 
    // This doesn't necessarily mean that the child was terminated!
    // See manual page section "ERRORS".

    if (errno == ECHILD) {
        // Child was already terminated by something else.
        p->status = TERMINATED;
    } else {
        perror("waitpid failed");
    }
} else if (code == 0) {
    // Child still in previous state.
    // Do nothing.
} else if (WIFEXITED(status)) {
    // Child exited.
    p->status = TERMINATED;
} else if (WIFSIGNALED(status)) {
    // Child killed by a signal.
    p->status = TERMINATED;
} else if (WIFSTOPPED(status)) {
    // Child stopped.
    p->status = SUSPENDED;
} else if (WIFCONTINUED(status)) {
    // This branch seems unnecessary, you should already know this
    // since you are the one that should kill(pid, SIGCONT) to make the
    // children continue.
    p->status = RUNNING; 
} else {
    // This should never happen!
    abort();
}

另请注意:

  1. 我在标志中添加 WUNTRACEDWCONTINUEDWIFSTOPPED() 不会发生,除非您使用 ptrace() 跟踪 child 或者您使用了WUNTRACED 标志,并且 WIFCONTINUED() 不会发生,除非使用 WCONTINUED
  2. codeppid 变量应该是 pid_t,而不是 intppid 变量似乎也不需要)。

无论如何,请考虑为 SIGCHLD 添加一个信号处理程序并在那里更新 children 状态。对于 terminates/stops/resuems 中的每一个 child,您的程序将收到一个 SIGCHLD。它更简单也更快(不需要在每个 child 进程上连续调用 waitpid())。