正确获取所有 child 个进程并收集退出状态

Properly reaping all child processes and collecting exit status

我想捕获由 parent 进程派生的所有 child 进程,然后收集最后一个 child 的退出状态。为此,我调用了 sigsuspend() 来等待 SIGCHLD 信号。当我收到 SIGCHLD 信号时,处理程序将在循环中调用 waitpid 直到它指示没有 children 剩余可收割。会设置退出状态,main会跳出循环终止。

然而,我注意到这是不正确的,因为所有 children 并不总是被收割。我该如何解决这个问题?

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

volatile sig_atomic_t exit_stat;

// Signal Handler
void sigchld_handler(int sig) {
    pid_t pid;
    int status;
    while(1) {  
        pid = waitpid(-1, &status, WNOHANG);
        if(pid <= 0) {break;}
        if(WIFEXITED(status)) {
            printf("%s", "Exited correctly.");
        }
        else {
            printf("%s", "Bad exit.");
        }
    }
    exit_stat = status;
}


// Executing code.
int main() {    
    signal(SIGCHLD, sigchld_handler);
    
    sigset_t mask_child;
    sigset_t old_mask;
    sigemptyset(&mask_child);
    sigaddset(&mask_child, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask_child, &old_mask);
    
    for(int i = 0; i < 5; i++) {
        int child_pid = fork();
        if(child_pid != 0) {
            //Perform execvp call.
            char* argv[] = {"echo", "hi", NULL};
            execvp(argv[0], argv);
        }
    }
    
    while(!exit_stat) {
        sigsuspend(&old_mask);
    }
    
    return 0;
}

正在将稍微修改过的评论转移到答案中。

waitpid()WNOHANG 选项表示“如果没有剩余 children,则立即 return,或者如果剩余 children,但他们仍然 运行”。如果你真的想等待所有 children 退出,要么省略 waitpid()WNOHANG 选项,要么简单地使用 wait() 代替。请注意,如果有任务在后台启动,它们可能不会终止很长时间(如果有的话)。 'the last child to die' 是否是正确的报告也取决于上下文。可以想象不合适的场景。

You're right, in this instance, I meant that "the last child to die" is the last child that was forked. Can I fix this by adding a simple condition to check if the returned pid of wait == the pid of the last forked child?

如果您对最近管道中的最后一个 child 感兴趣(例如 ls | grep … | sort … | wc 并且您想等待 wc),那么您知道 wc,你可以使用 waitpid(wc_pid, &status, 0) 来等待那个进程专门死掉。或者您可以使用循环来收集主体,直到找到 wc 的主体或获得 'no dead processes left'。在这一点上,您可以决定专门等待 wc PID,或者(更好)使用 waitpid() 而不使用 WNOHANG(或使用 wait()),直到某个进程终止 - 并且你可以再次决定它是否是 wc,如果不是,重复 WNOHANG 尸体收集过程以收集任何僵尸。重复直到找到 wc.

的尸体

And also, you said that background tasks may not terminate for a long time. By this, do you mean that waitpid(-1, &status, 0) will completely suspend all processes until a child is ready to be reaped?

waitpid(-1, &status, 0); 将使 parent 进程无限期地等待,直到某些 child 进程死亡,或者它将 return 因为没有 children 剩余等待(这表明存在管理错误;children 不应该在 parent 不知情的情况下死去)。

请注意,使用 'wait for any child' 循环可避免留下僵尸(children 已死亡但未等待)。这通常是个好主意。但是,当您当前感兴趣的 child 死亡时进行捕获可确保您的 shell 不会在不必要时徘徊等待。因此,您需要捕获已死亡 child 进程的 PID 和退出状态。