wait(2) - Linux 手册页

名称

wait, waitpid, waitid - 等待进程状态改变

概要

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status, int options);

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

glibc 的功能测试宏要求(参见 feature_test_macros(7))

waitid():
_SVID_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
|| /* 自 glibc 2.12 起: */ _POSIX_C_SOURCE >= 200809L

描述

所有这些系统调用都用于等待调用进程的子进程的状态改变,并获取有关状态已改变的子进程的信息。状态改变被认为是:子进程终止;子进程被信号停止;或者子进程被信号恢复。对于已终止的子进程,执行 wait 允许系统释放与该子进程关联的资源;如果不执行 wait,那么已终止的子进程将保持为“僵尸”状态(见下文“注意”)。

如果子进程已经改变了状态,那么这些调用会立即返回。否则,它们会阻塞,直到子进程改变状态或被信号处理程序中断(假设系统调用没有使用 sigaction(2) 的 SA_RESTART 标志自动重启)。在本页剩余部分中,一个状态已改变且尚未被这些系统调用之一等待的子进程被称为 可等待的 (waitable)

wait() 和 waitpid()

wait() 系统调用会暂停调用进程的执行,直到其一个子进程终止。调用 wait(&status) 等同于
waitpid(-1, &status, 0);
waitpid() 系统调用会暂停调用进程的执行,直到 pid 参数指定的子进程状态发生改变。默认情况下,waitpid() 只等待已终止的子进程,但可以通过 options 参数修改此行为,如下所述。

pid 的值可以是:

< -1

意味着等待任何进程组 ID 等于 pid 绝对值的子进程。

-1

意味着等待任何子进程。

0

意味着等待任何进程组 ID 等于调用进程进程组 ID 的子进程。

> 0

意味着等待进程 ID 等于 pid 值的子进程。

options 的值是以下常量中零个或多个的按位或(OR):
WNOHANG

如果没有子进程退出,则立即返回。

WUNTRACED

如果子进程已停止(但不是通过 ptrace(2) 追踪的),也会返回。对于已停止的 被追踪 子进程,即使未指定此选项也会提供状态。

WCONTINUED (自 Linux 2.6.10 起)
如果已停止的子进程通过接收 SIGCONT 信号而恢复,也会返回。
(仅限 Linux 的选项,见下文。)

如果 status 不为 NULL,wait() 和 waitpid() 会将状态信息存储在它指向的 int 中。该整数可以使用以下宏进行检查(这些宏接受整数本身作为参数,而不是像 wait() 和 waitpid() 那样接受指针!):

WIFEXITED(status)
如果子进程正常终止,即通过调用 exit(3) 或 _exit(2),或从 main() 返回,则返回 true。
WEXITSTATUS(status)
返回子进程的退出状态。这包含子进程在调用 exit(3) 或 _exit(2) 时指定的,或者作为 main() 中 return 语句的参数的 status 参数的最低有效 8 位。此宏应仅在 WIFEXITED 返回 true 时使用。
WIFSIGNALED(status)
如果子进程被信号终止,则返回 true。
WTERMSIG(status)
返回导致子进程终止的信号编号。此宏应仅在 WIFSIGNALED 返回 true 时使用。
WCOREDUMP(status)
如果子进程产生了核心转储 (core dump),则返回 true。此宏应仅在 WIFSIGNALED 返回 true 时使用。此宏未在 POSIX.1-2001 中指定,并且在某些 UNIX 实现(例如 AIX, SunOS)上不可用。仅在 #ifdef WCOREDUMP ... #endif 中使用。
WIFSTOPPED(status)
如果子进程被接收到的信号停止,则返回 true;这仅在调用使用 WUNTRACED 或子进程被追踪时才可能(见 ptrace(2))。
WSTOPSIG(status)
返回导致子进程停止的信号编号。此宏应仅在 WIFSTOPPED 返回 true 时使用。
WIFCONTINUED(status)
(自 Linux 2.6.10 起) 如果子进程通过接收 SIGCONT 信号而恢复,则返回 true。

waitid()

waitid() 系统调用(自 Linux 2.6.9 起可用)提供了更精确的控制,可以选择等待哪些子进程状态改变。

idtypeid 参数选择要等待的子进程,如下所示:

idtype == P_PID
等待进程 ID 与 id 匹配的子进程。
idtype == P_PGID
等待任何进程组 ID 与 id 匹配的子进程。
idtype == P_ALL
等待任何子进程;忽略 id
要等待的子进程状态改变通过在 options 中对以下一个或多个标志进行按位或来指定:
WEXITED

等待已终止的子进程。

WSTOPPED

等待被信号停止的子进程。

WCONTINUED

等待(之前被停止的)通过接收 SIGCONT 信号而恢复的子进程。

以下标志还可以与 options 进行按位或:
WNOHANG

waitpid() 相同。

WNOWAIT

使子进程保持在可等待状态;稍后可以使用 wait 调用再次获取子进程的状态信息。

成功返回时,waitid() 会填充 infop 指向的 siginfo_t 结构中的以下字段:
si_pid

子进程的进程 ID。

si_uid

子进程的实际用户 ID。(此字段在大多数其他实现中未设置。)

si_signo

总是设置为 SIGCHLD

si_status

要么是子进程的退出状态(如 _exit(2) 或 exit(3) 所给出的),要么是导致子进程终止、停止或继续的信号。si_code 字段可用于确定如何解释此字段。

si_code

设置为以下之一:CLD_EXITED (子进程调用了 _exit(2));CLD_KILLED (子进程被信号杀死);CLD_DUMPED (子进程被信号杀死并产生核心转储);CLD_STOPPED (子进程被信号停止);CLD_TRAPPED (被追踪的子进程已陷入);或 CLD_CONTINUED (子进程通过 SIGCONT 恢复)。

如果在 options 中指定了 WNOHANG 且没有处于可等待状态的子进程,则 waitid() 立即返回 0,且 infop 指向的 siginfo_t 结构的状态未指定。为了将此情况与存在可等待子进程的情况区分开来,请在调用前将 si_pid 字段清零,并在调用返回后检查该字段是否为非零值。

返回值

wait():成功时,返回已终止子进程的进程 ID;出错时,返回 -1。

waitpid():成功时,返回状态已改变的子进程的进程 ID;如果指定了 WNOHANG 且存在一个或多个 pid 指定的子进程但尚未改变状态,则返回 0。出错时,返回 -1。

waitid():成功时或如果指定了 WNOHANG 且没有 id 指定的子进程改变状态,则返回 0;出错时,返回 -1。在出错的情况下,每个这些调用都会将 errno 设置为适当的值。

错误

ECHILD

(对于 wait())调用进程没有任何未等待的子进程。

ECHILD

(对于 waitpid() 或 waitid())pid (waitpid()) 或 idtypeid (waitid()) 指定的进程不存在或不是调用进程的子进程。(如果 SIGCHLD 的动作被设置为 SIG_IGN,对于自己的子进程也可能发生这种情况。另见关于线程的 Linux 注意 部分。)

EINTR

未设置 WNOHANG 且捕获到了未阻塞的信号或 SIGCHLD;参见 signal(7)。

EINVAL

options 参数无效。

符合

SVr4, 4.3BSD, POSIX.1-2001。

说明

一个终止但尚未被等待的子进程会变成“僵尸”。内核维护关于僵尸进程的最小信息集(PID、终止状态、资源使用信息),以便父进程稍后可以执行 wait 来获取有关该子进程的信息。只要僵尸进程没有通过 wait 从系统中删除,它就会占用内核进程表中的一个位置,如果该表满了,将无法创建更多的进程。如果父进程终止,那么它的“僵尸”子进程(如果有)将被 init(8) 收养,后者会自动执行 wait 来清除这些僵尸。

POSIX.1-2001 规定,如果 SIGCHLD 的处理方式被设置为 SIG_IGN,或者为 SIGCHLD 设置了 SA_NOCLDWAIT 标志(参见 sigaction(2)),那么终止的子进程将不会成为僵尸进程,此时调用 wait() 或 waitpid() 将会阻塞,直到所有子进程都终止,然后失败并返回 errnoECHILD。(原始的 POSIX 标准未指定将 SIGCHLD 设置为 SIG_IGN 时的行为。请注意,即使 SIGCHLD 的默认处理方式是“忽略”,显式地将其处理方式设置为 SIG_IGN 也会导致对僵尸子进程产生不同的处理方式。)Linux 2.6 符合此规范。然而,Linux 2.4(及更早版本)并不符合:如果当 SIGCHLD 被忽略时调用 wait() 或 waitpid(),该调用的行为就像 SIGCHLD 没有被忽略一样,也就是说,该调用会阻塞直到下一个子进程终止,然后返回该子进程的进程 ID 和状态。

Linux 注意

在 Linux 内核中,内核调度的线程与进程没有本质区别。相反,线程只是使用 Linux 独有的 clone(2) 系统调用创建的进程;其他例程(如可移植的 pthread_create(3) 调用)是使用 clone(2) 实现的。在 Linux 2.4 之前,线程只是进程的一个特例,因此一个线程无法等待另一个线程的子进程,即使后者属于同一个线程组。然而,POSIX 规定了此类功能,自 Linux 2.4 起,线程可以(默认情况下也会)等待同一线程组中其他线程的子进程。

以下 Linux 特有的 options 用于与使用 clone(2) 创建的子进程一起使用;它们不能与 waitid() 一起使用:

__WCLONE
仅等待“克隆”子进程。如果省略,则仅等待“非克隆”子进程。(“克隆”子进程是指在终止时向其父进程发送除 SIGCHLD 之外的信号或不发送信号的子进程。)如果同时指定了 __WALL,则忽略此选项。
__WALL (自 Linux 2.4 起)
等待所有子进程,无论类型如何(“克隆”或“非克隆”)。
__WNOTHREAD (自 Linux 2.4 起)
不要等待同一线程组中其他线程的子进程。这在 Linux 2.4 之前是默认行为。

错误

根据 POSIX.1-2008,调用 waitid() 的应用程序必须确保 infop 指向一个 siginfo_t 结构(即,它是一个非 NULL 指针)。在 Linux 上,如果 infop 为 NULL,waitid() 会成功,并返回被等待子进程的进程 ID。应用程序应避免依赖此不一致、非标准且不必要的功能。

示例

以下程序演示了 fork(2) 和 waitpid() 的使用。该程序创建一个子进程。如果程序未提供命令行参数,则子进程使用 pause(2) 暂停执行,以允许用户向子进程发送信号。否则,如果提供了命令行参数,则子进程立即退出,使用命令行上提供的整数作为退出状态。父进程执行一个循环,使用 waitpid() 监控子进程,并使用上述描述的 W*() 宏来分析等待状态值。

以下 shell 会话演示了该程序的使用

$ ./a.out &
Child PID is 32360
[1] 32359
$ kill -STOP 32360
stopped by signal 19
$ kill -CONT 32360
continued
$ kill -TERM 32360
killed by signal 15
[1]+  Done                    ./a.out
$

程序源码

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

int
main(int argc, char *argv[])
{
    pid_t cpid, w;
    int status;

   cpid = fork();
    if (cpid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

   if (cpid == 0) {            /* Code executed by child */
        printf("Child PID is %ld\n", (long) getpid());
        if (argc == 1)
            pause();                    /* Wait for signals */
        _exit(atoi(argv[1]));

   } else {                    /* Code executed by parent */
        do {
            w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
            if (w == -1) {
                perror("waitpid");
                exit(EXIT_FAILURE);
            }

           if (WIFEXITED(status)) {
                printf("exited, status=%d\n", WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("killed by signal %d\n", WTERMSIG(status));
            } else if (WIFSTOPPED(status)) {
                printf("stopped by signal %d\n", WSTOPSIG(status));
            } else if (WIFCONTINUED(status)) {
                printf("continued\n");
            }
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
        exit(EXIT_SUCCESS);
    }
}

参见

_exit(2), clone(2), fork(2), kill(2), ptrace(2), sigaction(2), signal(2), wait4(2), pthread_create(3), credentials(7), signal(7)

引用自

acct(5), clock(3), csh(1), exit(2), explain(1), explain(3), explain_lca2010(1), explain_wait(3), explain_wait_or_die(3), filter_wait(3), getrusage(2), ibv_fork_init(3), intro(1), ksh(1), ksh93(1), man-pages(7), mksh(1), nal_connection_new(2), perlfunc(1), perlglossary(1), perlvar(1), prctl(2), pthread_exit(3), pthreads(7), shellout(3), strace(1), sudo_plugin(8), system(3), times(2), vfork(2)