CUBE SUGAR CONTAINER

技術系のこと書きます。

Linux: fork(2) で子プロセスの終了理由を判定する

今回は fork(2) で子プロセスの終了理由を判定してみる。 結論から先に述べると、子プロセスの終了を待つとき wait(2) に int のポインタを渡すと終了理由をセットしてくれる。 それをマクロで判定していけば良い。

linuxjm.osdn.jp

使った環境は次のとおり。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
$ uname -rm
5.4.0-91-generic aarch64

もくじ

下準備

あらかじめ C のビルドに必要な build-essential をインストールしておく。

$ sudo apt-get update
$ sudo apt-get install -y build-essential

子プロセスの終了理由を判定してみる

以下に、子プロセスの終了理由を「exit(3)」「シグナル」「その他」に判定するサンプルコードを示す。 前述したとおり、親プロセスが wait(2) で子プロセスの終了を待つときに、引数として int のポインタを渡してやる。 すると、終了理由を示す数値がセットされるので、それをマクロで判定してやる。 子プロセスでは bash を起動している。

#define _GNU_SOURCE

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

int main(int argc, char *argv[]) {
    // プロセスを fork(2) でフォークする
    pid_t pid = fork();
    if (pid < 0) {
        fprintf(stderr, "Failed to fork a new process: %s\n", strerror(errno));
        exit(-1);
    }

    if (pid != 0) {
        // PID が非ゼロは親プロセス
        // wait(2) で子プロセスの完了を待つ
        int status;
        wait(&status);

        // 子プロセスの終了の仕方を出力する
        if (WIFEXITED(status)) {
            // WIFEXITED() が非ゼロなら exit(3) による終了
            // WEXITSTATUS() で終了コードが得られる 
            printf("exit(3), status=%d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            // WIFSIGNALED() が非ゼロならシグナルによる終了
            // WTERMSIG() でシグナル番号が得られる
            printf("signal, sig=%d\n", WTERMSIG(status));
        } else {
            // それ以外の終了
            printf("aborted");
        }

        exit(EXIT_SUCCESS);
    }

    // 以降は子プロセスでの処理

    // 子プロセスでシェル (Bash) を起動する
    char* const exec_argv[] = {"bash", NULL};
    if (execvp(exec_argv[0], exec_argv) < 0) {
        fprintf(stderr, "Failed to exec \"%s\": %s\n", exec_argv[0], strerror(errno));
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

ちなみに、理由を知る必要がないときは wait(2) の引数に NULL を指定すれば良い。

上記をコンパイルする。

$ gcc -Wall example.c 

現在の PID は 927 だった。

$ echo $$
927

先ほどコンパイルしたバイナリを実行する。

$ ./a.out

すると、fork(2) した子プロセスで新たに bash が起動する。

$ echo $$
1135

試しに組み込みの exit コマンドでプロセスを終了してみよう。 ステータスコードには適当に 10 を指定する。

$ exit 10
exit
exit(3), status=10

上記のとおり、ちゃんと WIFEXITED のブロックに入ってステータスコードが得られた。

続いてはシグナルで終了させてみよう。

$ echo $$
927
$ ./a.out
$ echo $$
1536

子プロセスに KILL シグナルを送る。

$ kill -KILL 1536
signal, sig=9

上記のとおり、ちゃんと WIFSIGNALED のブロックに入ってシグナルによる終了と判定された。

いじょう。 それ以外のパターンは、どう確認すれば良いのかな。