wait(2) プロセスの状態変化を待つ

Other Alias

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 と POSIX のインターフェイスである。
                   生のシステムコールについての情報は「注意」の節を参照。 */

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 が実行されなかった場合には、終了した子プロセスは 「ゾンビ」状態で残り続ける (下記の注意の章を参照のこと)。

子プロセスの状態変化がすでに発生していた場合、これらのコールは すぐに復帰する。それ以外の場合は、子プロセスの状態変化が起こるか、 シグナルハンドラによりシステムコールが中断されるまで、 停止 (block) する (後者は、 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 が呼び出したプロセスのものと等しい 子プロセスを待つ。
> 0
プロセスID が pid に等しい子プロセスを待つ。

options の値は次の定数の 0 個以上の論理和である:

WNOHANG
状態変化が起こった子プロセスがない場合にすぐに復帰する。
WUNTRACED
子プロセスが停止した場合にも復帰する (子プロセスが ptrace(2) でトレースされている場合は除く)。 このオプションが指定されていない場合でも、停止したプロセスが 「トレース (traced)」されていれば、子プロセスの状態が報告される。
WCONTINUED (Linux 2.6.10 以降)
停止した子プロセスが SIGCONT の配送により再開した場合にも復帰する。

(Linux 専用オプションについては後述する)

status が NULL でなければ、 wait() や waitpid() は status で指す int に状態情報を格納する。 この整数は以下のマクロを使って検査できる。 (これらのマクロの引き数には、 wait() や waitpid() が書き込んだ整数そのものを指定する。ポインタではない!)

WIFEXITED(status)
子プロセスが正常に終了した場合に真を返す。 「正常に」とは、 exit(3) か _exit(2) が呼び出された場合、もしくは main() から復帰した場合である。
WEXITSTATUS(status)
子プロセスの終了ステータスを返す。 終了ステータスは status 引き数の下位 8ビットで構成されており、 exit(3) や _exit(2) の呼び出し時に渡された値、もしくは main() の return 文の 引き数として指定された値である。 このマクロを使用するのは WIFEXITED が真を返した場合だけにすべきである。
WIFSIGNALED(status)
子プロセスがシグナルにより終了した場合に真を返す。
WTERMSIG(status)
子プロセス終了の原因となったシグナルの番号を返す。 このマクロを使用するのは WIFSIGNALED が真を返した場合だけにすべきである。
WCOREDUMP(status)
子プロセスがコアダンプを生成した場合に真を返す。 このマクロを使用するのは WIFSIGNALED が真を返した場合だけにすべきである。 このマクロは POSIX.1-2001 では規定されておらず、 (AIX, SunOS などの) いくつかの UNIX の実装では利用できない。 必ず #ifdef WCOREDUMP ... #endif で括って使用すること。
WIFSTOPPED(status)
子プロセスがシグナルの配送により停止した場合に真を返す。 これが真になるのは、システムコールが WUNTRACED を指定して呼び出された場合か、子プロセスがトレースされている場合 (ptrace(2) 参照) だけである。
WSTOPSIG(status)
子プロセスを停止させたシグナルの番号を返す。 このマクロを使用するのは WIFSTOPPED が 0 以外を返した場合だけにすべきである。
WIFCONTINUED(status)
(Linux 2.6.10 以降) 子プロセスが SIGCONT の配送により再開した場合に真を返す。

waitid()

waitid() システムコール (Linux 2.6.9 以降で利用可能) を使うと、 子プロセスのどの状態変化を待つかについてより細かな制御ができる。

引き数 idtypeid でどの子プロセスを待つかを選択する:

idtype == P_PID
プロセスID が id と一致する子プロセスを待つ。
idtype == P_PGID
プロセスグループID が id と一致する子プロセスを待つ。
idtype == P_ALL
子プロセス全部を対象に待つ。 id は無視される。

子プロセスのどの状態変化を待つかは以下のフラグで指定する (options には 1個以上のフラグの論理和をとって指定する):

WEXITED
子プロセスの終了を待つ。
WSTOPPED
子プロセスがシグナルの配送により停止するのを待つ。
WCONTINUED
(停止していた) 子プロセスが SIGCONT が配送されて再開するのを待つ。

さらに以下のフラグを論理和の形で options に指定できる:

WNOHANG
waitpid() と同様。
WNOWAIT
waitable 状態のプロセスをそのままにする。この後で 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 (シグナルにより子プロセスが kill された); CLD_DUMPED (シグナルにより子プロセスが kill され、コア・ダンプが行われた); CLD_STOPPED (シグナルにより子プロセスが停止した); CLD_TRAPPED (トレースされていた子プロセスがトラップを受信した); CLD_CONTINUED (SIGCONT により子プロセスが再開された)。

WNOHANGoptions に指定されていて、 waitable 状態の子プロセスがなかった場合には、 waitid() はすぐに 0 を返す。このとき、 infop が指す siginfo_t 構造体の内容は不定である。 この場合を waitable 状態の子プロセスがあった場合と区別するには、 waitid() を呼び出す前に si_pid を 0 にしておき、コールが復帰した後でこのフィールドが 0 以外の値かどうか をチェックすればよい。

返り値

wait(): 成功すると、終了した子プロセスのプロセスID を返す。 エラーの場合 -1 を返す。

waitpid(): 成功すると、状態が変化した子プロセスのプロセスID を返す。 WNOHANG が指定されていて、 pid で指示された子プロセスが一つ以上存在するが、どの子プロセスでも 状態変化が起こっていなかった場合は、 0 を返す。 エラーの場合 -1 を返す。

waitid(): 成功すると 0 を返す。 WNOHANG が指定されていて、 pid で指示された子プロセスで状態変化が起こっていなかった場合にも 0 を返す。 エラーの場合 -1 を返す。 エラーの場合、これらのシステムコールはいずれも errno に適切な値を設定する。

エラー

ECHILD
(wait() の場合) 呼び出し元プロセスには、wait を行っていない子プロセスはない。
ECHILD
(waitpid() か waitid() の場合) pid (waitpid()) か idtypeid (waitid()) で指定したプロセスが存在しないか、呼び出し元プロセスの子プロセスでない (SIGCHLD の動作に SIG_IGN を設定した場合には、自分自身の子プロセスでも起こりうる。 スレッドに関しては「Linux での注意」の節も参照すること)。
EINTR
WNOHANG が設定されておらず、禁止 (block) されていないシグナルや SIGCHLD を受信した。 signal(7) 参照。
EINVAL
options 引き数が不正である。

準拠

SVr4, 4.3BSD, POSIX.1-2001.

注意

終了したが、wait されていない子プロセスは「ゾンビ」になる。 後で親プロセスが wait を実行して子プロセスについての情報を取得できるように、 カーネルはゾンビプロセスについて最小限の情報 (PID、終了ステータス、 リソース使用状況) を保持する。 ゾンビプロセスは、 wait によってシステムから削除されない限り、 カーネルのプロセステーブルの 1 エントリを消費する。このプロセステーブルが 一杯になると、新たにプロセスを作ることができなくなる。 親プロセスが終了すると、その親プロセスの「ゾンビ」の 子プロセスは (もしあれば) init(8) の養子となる。 init(8) は wait を自動的に実行し、ゾンビを削除する。

POSIX.1-2001 では以下のように規定されている。 SIGCHLD の動作が SIG_IGN に設定されたか、 SIGCHLD に対して SA_NOCLDWAIT フラグが設定された場合 (sigaction(2) 参照)、終了した子プロセスはゾンビにはならず、 wait() や waitpid() の呼び出しは全ての子プロセスが終了するまで停止し、 子プロセスが全部終了した後 errnoECHILD を設定して失敗する。 (もともとの POSIX 標準は SIGCHLDSIG_IGN を設定した場合の振る舞いを未規定のままにしている。 SIGCHLD のデフォルトの動作が「無視」であるにもかかわらず、 SIGCHLD の動作として SIG_IGN を明示的に設定した場合にはゾンビプロセスの子プロセスの扱いが 異なる点に注意すること。) Linux 2.6 はこの仕様に準拠している。 しかし、Linux 2.4 (とそれ以前のバージョン) はそうではない: SIGCHLD が無視される状態で wait() または waitpid() が呼び出された場合、 SIGCHLD が無視されていないかのように振る舞う。 つまり、呼び出しによって次の子プロセスの終了までブロックされ、 終了した子プロセスの PID と状態が返される。

Linux での注意

Linux カーネルでは、カーネルによってスケジュールされるスレッドは プロセスと明確に区別できる構成要素ではない。スレッドは Linux 固有の clone(2) システムコールを使用して生成されるプロセスに過ぎない。 移植性のある pthread_create(3) コールのような他のルーチンは clone(2) を使用して実装されている; これらでは waitid() を使うことはできない。 Linux 2.4 より前では、スレッドは単に特殊なプロセスであったので、 例え同じスレッドグループであっても、 あるスレッドが別のスレッドの子プロセスが終了するのを待つことは出来なかった。 しかし、POSIX ではこのような機能を規定しており、 Linux 2.4 以降では、あるスレッドが同じスレッドグループの他のスレッドの 子プロセスが終了するのを待つことができるようになった。 そして将来はこれがデフォルトの動作になるであろう。

clone(2) を用いて作られた子プロセスには、以下の Linux 固有の options が使用できる。

__WCLONE
"clone" な子プロセスだけを待つ。 指定されなかった場合は非 "clone" な子プロセスだけを待つ ("clone" な子プロセスは、終了時に親プロセスへ全くシグナルを送らないか、 SIGCHLD 以外のシグナルを送る)。 このオプションは __WALL も指定された場合は無視される。
__WALL (Linux 2.4 以降)
"clone" であるかないかに関わらず、 全ての子プロセスを待つ。
__WNOTHREAD (Linux 2.4 以降)
同じスレッドグループの他のスレッドの子プロセスは待たない。 Linux 2.4 より前ではデフォルトであった。

生の waitid() システムコールは struct rusage * 型の第 5 引数を取る。 この引数が NULL 以外の場合、 この引数が子プロセスのリソース使用状況を返すのに使用される。 これは wait4(2) と同じ方法である。 詳細は getrusage(2) を参照。

バグ

POSIX.1-2008 によると、 waitid() を呼び出すアプリケーションは、 infopsiginfo_t 構造体を指していること (つまり infop が NULL でないポインタであること) を保証しなければならない。 Linux では、 infop が NULL の場合、 waitid() は成功し、wait している子プロセスのプロセス ID を返す。 アプリケーションは、この食い違った、非標準で、不必要な機能に依存しないようにすべきである。

以下のプログラムは、 fork(2) と waitpid() の使用方法の例を示している。 このプログラムでは子プロセスを生成する。 コマンドライン引き数が指定されなかったときは、 子プロセスは pause(2) を使ってその実行を一時停止し、ユーザがその子プロセスに シグナルを送信できるようにする。 コマンドライン引き数が指定された場合は、 子プロセスは直ちに終了し、 コマンドラインで指定された整数を終了ステータスとして使用する。 親プロセスは、 waitpid() を使って子プロセスを監視し、 wait のステータス値を上記の W*() マクロを使って解析するという ループを実行する。

以下のシェルのセッションはこのプログラムの使用例を示したものである。

$ ./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);
    }
}

この文書について

この man ページは Linux man-pages プロジェクトのリリース 3.65 の一部である。 プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。