HOWTO · C

使用 C 语言中的 waitpid 函数

本文介绍了如何在 C 语言中使用 waitpid 函数。

本页内容

父进程需要回收子进程状态时,可以使用 waitpid 等待指定的子进程,或等待任意符合条件的子进程。它的参数用于选择子进程以及要报告的状态变化;返回的状态必须使用 <sys/wait.h> 中的宏解码,不能直接当作退出码。

使用 waitpid 等待指定的子进程

进程可以通过 fork 创建子进程。子进程从调用 fork 的位置继续执行,而父进程会收到子进程的进程 ID。子进程终止后,父进程应调用 wait 系列函数回收它,以取得其终止状态并完成回收。

调用形式为 waitpid(pid, &status, options)。正数 pid 用于选择对应的子进程。-1 会选择状态符合选项条件的任意子进程,wait 使用的就是这种选择方式;0 会选择调用进程所在进程组中的子进程;小于 -1 的值则会选择进程组 ID 等于 pid 绝对值的子进程。因此,waitpid 可以用来指定要等待的子进程,但它并不是 wait 系列中唯一可用于等待子进程的接口。

第二个参数指向一个整数;报告子进程状态时,内核会将编码后的状态写入其中。需要取得状态时,请传入有效指针。第三个参数是选项位掩码。选项设为 0 时,调用会等待所选子进程终止。如果当前没有符合条件的状态变化,调用就会阻塞。

选择要报告的子进程事件

WNOHANG 会让调用以非阻塞方式运行:如果所选子进程没有可报告的状态变化,waitpid 会立即返回 0。这样,父进程可以先处理其他工作,之后再检查一次。返回正数时,该值是其状态已写入的子进程 ID。WUNTRACED 还会报告尚未报告过的已停止子进程;WCONTINUED 则会报告停止后恢复运行的子进程。这些选项决定哪些状态变化可以满足本次调用;单次调用不会持续监视之后发生的所有状态变化。可以使用按位 OR 运算符组合标志,例如 WUNTRACED | WCONTINUED。

解码 waitpid 返回的状态

状态整数经过编码,应使用相应的宏进行判断。WIFEXITED(status) 用于判断子进程是否正常终止;若是,则可通过 WEXITSTATUS(status) 取得退出码。WIFSIGNALED(status) 用于判断子进程是否因信号而终止,WTERMSIG(status) 会给出该信号。请求报告已停止的子进程时,可用 WIFSTOPPED(status) 和 WSTOPSIG(status) 判断停止状态及对应信号。在系统支持且调用请求报告继续运行状态时,WIFCONTINUED(status) 可用于识别已恢复运行的子进程。部分系统提供 WCOREDUMP;使用它的代码应通过预处理器条件检查其是否可用。这些宏由 <sys/wait.h> 声明。

只有在 WIFEXITED 为真时才调用 WEXITSTATUS;只有在 WIFSIGNALED 为真时才调用 WTERMSIG。这个整数本身并不是子进程的退出码。使用对应的宏可以避免依赖因实现而异的状态位布局。

处理返回值和错误

waitpid 返回值大于零时,表示已回收状态的子进程 ID。返回 0 表示当前没有符合条件的状态可供报告,这种情况会在使用 WNOHANG 时出现。返回 -1 表示发生错误;请检查 errno 来判断原因。例如,ECHILD 表示没有符合选择条件的子进程;EINTR 表示等待期间捕获到信号,导致调用被中断。如果程序应在 EINTR 后继续等待,就重试调用;否则应依照应用程序的信号处理策略处理中断。不使用 WNOHANG 时,如果所选子进程仍在运行且没有发生所请求的状态变化,调用也可能无限期阻塞。

运行完整的子进程示例

这个 POSIX 示例会创建一个子进程,专门等待它终止;如果等待因信号而中断,就重试,并打印解码后的退出状态。示例使用 fork 和 waitpid,不依赖其他程序或交互式输入。代码适用于装有 C 编译器的 POSIX 环境,并非可移植的 ISO C 示例。所提供的执行证据记录了成功运行:退出码为 0,输出如下。

运行此示例:

本节中的代码以及显示的标签或输出仍为英文。

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

int main(void) {
    pid_t child = fork();
    if (child == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }

    if (child == 0) {
        _exit(7);
    }

    int status;
    pid_t result;
    do {
        result = waitpid(child, &status, 0);
    } while (result == -1 && errno == EINTR);

    if (result == -1) {
        perror("waitpid");
        return EXIT_FAILURE;
    }

    if (WIFEXITED(status)) {
        printf("child exit status: %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("child terminated by signal: %d\n", WTERMSIG(status));
    }

    return EXIT_SUCCESS;
}
child exit status: 7