ゾンビプロセスを作る

ゾンビプロセスというものがありますが、どんな時にゾンビプロセスができるのかなんとなくしか知らなかったので、実際に作って観察してみることにしました。

以下のコードで検証します。

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

int main(int argc, char *argv[]) {
    pid_t pid;

    pid = fork();
    if (pid == 0) { // 子プロセス
        execl("/bin/sleep", "sleep", "5", NULL);
    } else { // 親プロセス
        int status;

        // sleep(10);
        // exit(0);

        waitpid(pid, &status, 0);
        printf("PID %d has been finished; exit status: %d", pid, status);
        exit(0);
    }
}

このプログラム自体がやっていることは、まず fork して、子プロセスは exec で 5 秒 sleep し、親プロセスは子プロセスの終了を待ち、終了したら PID とステータスコードを出力するというものです。(fork や exec についての解説は省略します。) この状態ではまだゾンビプロセスは生まれません。

では検証を始める前に、いくつか準備しておきましょう。 上記のコードを parent.c として保存し、コンパイルします。

$ gcc -o parent parent.c

次に、各親子プロセスの様子を観察するために pswatch しておきます。 これで 1 秒ごとのプロセスの状態が観察できます。

$ watch -n 1 'ps aux | grep -e parent -e sleep | grep -v grep'

それでは実行してみましょう。

検証 1: お行儀よく wait する

parent を実行すると、以下のように親子両方のプロセスが出てきて、5 秒後に両方のプロセスが終了します。

username          33080   0.0  0.0  4268020    672 s001  S+    8:17PM   0:00.00 sleep 5
username          33074   0.0  0.0  4268012    792 s001  S+    8:17PM   0:00.00 ./parent

意図通り動いていますね。

検証 2: 親プロセスが wait せずにいきなり exit する

次に、exit() のみコメントを外します。

... (snip) ...
        // sleep(10);
        exit(0);

        waitpid(pid, &status, 0);
... (snip) ...

再びコンパイルして実行すると、sleep だけが表示され、5 秒後に終了します。

username          33080   0.0  0.0  4268020    672 s001  S+    8:17PM   0:00.00 sleep 5

子プロセスは、親プロセスが死ぬと PID 1 のプロセス (init, systemd, launchd など) を新たな親プロセスとします。 そのため、parent は子プロセスを産んですぐに死にますが、子プロセスは 5 秒 sleep した後にそのまま終了します。 これはゾンビプロセスではありません。

検証 3: 子プロセスが終了しても、親プロセスは wait も exit もしない

次に sleep() のコメントも外します。 これにより、子プロセスが 5 秒で終了した後しばらくの間、親プロセスが wait も exit もせずにいる状態が作り出せます。

... (snip) ...
        sleep(10);
        exit(0);

        waitpid(pid, &status, 0);
... (snip) ...

再びコンパイルして実行すると、5 秒後に子プロセスが (sleep) という状態になり、10 秒後に両方のプロセスが同時に終了します。

username          39933   0.0  0.0        0      0 s001  Z+    8:17PM   0:00.00 (sleep)
username          39932   0.0  0.0  4268012    792 s001  S+    8:17PM   0:00.00 ./parent

子プロセスは 5 秒後に終了しますが、その直後は親プロセスがまだ sleep している状態です。 プロセスを管理しているカーネルは親プロセスがこの後 wait を呼ぶのかどうか、呼ぶとしたらいつ呼ぶのかわからないですから、子プロセスのステータスコードをいつまでも保持しておかなければなりません。 この状態になった子プロセスがゾンビプロセスです。

ps aux コマンドでは、8 列目にプロセスのステータスが出ます。(sleep) となっているプロセスが、Z+ というふうに、ゾンビプロセスであることが表されていますね。

ということで、fork したあとはちゃんと wait しないとゾンビプロセスが生まれてしまうことが無事確認できました。