fork, exec, pipe

  • UNIX(Linux)のプロセス起動は createProcess というような1つのシステムコールではなく、fork と exec の組み合わせ(Fork-Exec)で実現される。
  • fork は現在のプロセスをまるまるコピーし、別々の実行コンテキストを作る。
    • fork への戻り値をそれぞれのコンテキストで変えて区別させる。
    • 仮想メモリ空間の情報もコピーされる。
  • exec は自プロセスを別のバイナリ実行用プロセスに置き換える。
  • fork と exec が分かれており、かつ、fork が親プロセスの仮想メモリ空間をコピーしていることで、親プロセスと子プロセス(または子プロセス同士)の間の通信用のパイプを作る処理を fork と exec の間で行うことができる。

「fork」『フリー百科事典 ウィキペディア日本語版』より

Fork-Execは、UNIXで一般的に使われる手法であり、新たなプログラムをプロセスとして実行する。fork()は親プロセスを2つの同一内容のプロセスに(フォークの先のように)分岐させるシステムコールである。fork()によって子プロセスが親プロセスのコピーとして生成され、exec()システムコールを呼び出すことで(子プロセス)自身の内容を置き換える。

子プロセスがexec()を呼び出すと、そのアドレス空間の内容は全て失われ、指定されたプログラムを実行するためのアドレス空間のマッピングが設定される。これをオーバーレイと呼ぶ。アドレス空間は全て置き換えられるが、オープン済みファイルのファイル記述子群は close-on-exec が指定されたときだけ exec()時に自動的にクローズされる。この特徴を利用して、fork()を呼び出す前にパイプを作成しておき、exec()で指定された新しいプログラムとの通信を行うというUNIX特有の手法が実現されている。

概念図


動作確認用コード(C++)

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

#define READ  0
#define WRITE 1

int pfd[2];


void doChildProcess() {
    char *argv[16];

    close(pfd[WRITE]);
    dup2(pfd[READ], STDIN_FILENO);

    argv[0] = "/usr/bin/tr";
    argv[1] = "[:lower:]";
    argv[2] = "[:upper:]";
    argv[3] = NULL;
    execv(argv[0], argv);
}


int main() {
    pid_t cpid;
    char *msg;

    if ( pipe(pfd) == -1 ) { perror("pipe"); exit(EXIT_FAILURE); }

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

    if ( cpid == 0  )
        doChildProcess(); // NEVER to return

    // doParentProcess...
    close(pfd[READ]);

    msg = "hello world!\n";
    write(pfd[WRITE], msg, strlen(msg));
    close(pfd[WRITE]);
    wait(NULL);

    exit(EXIT_SUCCESS);
}