捕获子进程的退出状态码

19

我有一个函数,它会fork出一个新进程,并复制输入和输出缓冲区的文件描述符,并且在一个叫做cmd的字符串参数上运行execl命令:

static pid_t
c2b_popen4(const char* cmd, int pin[2], int pout[2], int perr[2], int flags)
{
    pid_t ret = fork();

    if (ret < 0) {
        fprintf(stderr, "fork() failed!\n");
        return ret;
    }
    else if (ret == 0) {
        /*                                                                                                                                                                                                                                                                                                                  
           Assign file descriptors to child pipes (not shown)...                                                                                                                                                                                                                                                                                               
        */
        execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
        fprintf(stderr, "execl() failed!\n");
        exit(EXIT_FAILURE);
    }
    else {
        /*                                                                                                                                                                                                                                                                                                                  
           Close parent read and write pipes (not shown)...                                                                                                                                                                                                                                                                                              
        */
        return ret;
    }
    return ret;
}

每个cmd实例只要我的测试输入正确,就可以正确处理我的数据。

当错误的数据被传递给子进程时,我的父程序将运行完成并以非错误状态代码0退出。

如果我故意输入错误的数据——有目的地试图让其中一个cmd实例按预期方式失败——我想知道如何捕获该cmd的退出状态,以便在终止之前从父程序发出正确的错误状态代码。

通常怎么做呢?

1个回答

27

你可以通过wait()的第一个参数,或者waitpid()的第二个参数获得子进程的退出状态,然后使用宏WIFEXITEDWEXITSTATUS

例如:

pid_t ret = c2b_popen4("myprog", pin, pout, perr, 0);

if ( ret > 0 ) {
    int status;

    if ( waitpid(ret, &status, 0) == -1 ) {
        perror("waitpid() failed");
        exit(EXIT_FAILURE);
    }

    if ( WIFEXITED(status) ) {
        int es = WEXITSTATUS(status);
        printf("Exit status was %d\n", es);
    }
}

一个简化的工作示例:

failprog.c

int main(void) {
    return 53;
}

shellex.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{
    pid_t p = fork();
    if ( p == -1 ) {
        perror("fork failed");
        return EXIT_FAILURE;
    }
    else if ( p == 0 ) {
        execl("/bin/sh", "bin/sh", "-c", "./failprog", "NULL");
        return EXIT_FAILURE;
    }

    int status;
    if ( waitpid(p, &status, 0) == -1 ) {
        perror("waitpid failed");
        return EXIT_FAILURE;
    }

    if ( WIFEXITED(status) ) {
        const int es = WEXITSTATUS(status);
        printf("exit status was %d\n", es);
    }

    return EXIT_SUCCESS;
}

输出:

paul@thoth:~/src/sandbox$ ./shellex
exit status was 53
paul@thoth:~/src/sandbox$ 

waitpid()函数会阻塞,直到给定进程ID的进程退出。由于您使用了popen()名称并向其传递了管道,因此您的子进程可能不会很快终止,如果调用成功,则那可能不是检查它的正确位置。您可以将WNOHANG作为第三个参数传递给waitpid()以检查进程是否已终止,并在子进程尚未退出时返回0,但您必须小心关于何时这样做,因为您无法得到关于哪个进程将运行的保证。如果从c2b_popen4()返回后立即使用WNOHANG调用waitpid(),则可能会在您的子进程有机会执行并带有错误代码终止之前返回0,从而使它看起来像执行成功,当实际上它即将失败。

如果该进程立即死亡,您将无法从管道中读取和写入数据,因此一种选择是在第一次尝试这样做时检查waitpid(),以检查read()write()是否因子进程终止而失败。如果事实证明是这样,您可以检索退出状态并随后退出您的整个程序。

还有其他可能的策略,包括捕获SIGCHLD信号,因为每当其中一个子进程死亡时,它都会被引发。例如,从信号处理程序直接调用_exit()是可以的,在等待子进程(在信号处理程序中调用waitpid()也是安全的)并获取其退出状态后退出。


1
当我运行你的第一个代码块(即“例如”示例)时,进程在waitpid()调用处停顿。 - Alex Reynolds
1
@AlexReynolds:这是默认的工作方式,参见答案更新。您需要一些策略来检测并处理子进程提前终止的可能性。 - Crowman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接