[徹底解説] Linux プロセス生成のしくみ

Linux においてプロセスは、fork(2) および clone(2) システムコールを使用することにより生成された後、exec(2) システムコールを使用して新規プログラムが実行される。

まずは、実際のプロセス生成を模擬した以下のサンプルプログラムを実行し、プロセス生成のしくみについて理解を深めていく。

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main(void)
{
int pid = fork();

if (pid == -1) {
fprintf(stderr, "プロセスの複製に失敗しました。\n");
return -1;
} else if (pid == 0) { // 子プロセスの場合
fprintf(stdout, "ls コマンド実行のための子プロセス生成 (PID: %d)\n", getpid());

// システムコールするため、引数および環境変数を設定
char *argv[] = { "/bin/ls", "-la", 0 };
char *envp[] = { "HOME=/", "PATH=/bin:/usr/bin", "USER=brandon", 0 };

// execve(2) システムコールにより、プロセスを実行可能イメージに置き換える
execve(argv[0], &argv[0], envp);

// execve(2) からのリターンがない場合は異常終了
fprintf(stderr, "エラーが発生しました。\n");
return -1;
} else if (pid > 0) { // 親プロセスの場合
int status;

fprintf(stdout, "親プロセスは子プロセスの実行完了するまで待機 (PID: %d)\n", getpid());
wait(&status);
fprintf(stdout, "子プロセスの実行完了 (リターンコード: %i)、および親プロセスの終了\n", status);
}
return 0;
}

サンプルコードを実行すると以下のような出力を得られる。
この出力結果より、まず、子プロセスが生成され、親プロセスは子プロセス完了を待機し、子プロセスの処理終了を受けて親プロセスが終了していることがわかる。

$ gcc -o fork_exec fork_exec.c
$ strace -o /tmp/fork_exec.log -f ./fork_exec
親プロセスは子プロセスの実行完了するまで待機 (PID: 18040)
ls コマンド実行のための子プロセス生成 (PID: 18041)
total 16
drwxr-xr-x 2 root root 40 Oct 7 21:30 .
drwxr-xr-x 3 root root 19 Oct 7 21:23 ..
-rwxr-xr-x 1 root root 8792 Oct 7 21:25 fork_exec
-rw-r--r-- 1 root root 1181 Oct 7 21:25 fork_exec.c
子プロセスの実行完了 (リターンコード: 0)、および親プロセスの終了

strace の出力ファイルを参照すると、より詳細な内容を把握することができる。

  1. 親プロセスから子プロセスをクローン

    18040 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb6cbfeaa10) = 18041
  2. 親プロセスは子プロセスの実行が完了するまで待機

    // "親プロセスは子プロセスの実行完了するまで待機 (PID: 18040)"
    18040 wait4(-1, <unfinished ...>
  3. 子プロセスが execve() によって実行可能状態に遷移

    // "ls コマンド実行のための子プロセス生成 (PID: 18041)"
    18041 execve("/bin/ls", ["/bin/ls", "-la"], [/* 3 vars */]) = 0
  4. ls コマンドの実行・表示

    // "total 16"
    // "drwxr-xr-x 2 root root 40 Oct 7 21:30 ."
    // "drwxr-xr-x 3 root root 19 Oct 7 21:23 .."
    // "-rwxr-xr-x 1 root root 8792 Oct 7 21:25 fork_exec"
    // "-rw-r--r-- 1 root root 1181 Oct 7 21:25 fork_exec.c"
  5. 子プロセスの終了 (リターンコード: 0)

    18041 exit_group(0)                     = ?
    18041 +++ exited with 0 +++
  6. 親プロセスで子プロセスの終了を受理・処理の再開

    18040 <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 18041
    ...
    18040 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=18041, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
    ...
    // "子プロセスの実行完了 (リターンコード: 0)、および親プロセスの終了"
  7. 親プロセスの終了 (リターンコード: 0)

    18040 exit_group(0)                     = ?
    18040 +++ exited with 0 +++