fork()は、C言語(UNIX系)で子プロセスを起動する関数。
|
|
fork()を使うと、実行中の状態(コンテキスト)すなわち現在のプロセスが使っているメモリーの中身が全て複製され、子プロセスが起動される。
したがって、ローカル変数やポインター等もそのまま使える。
fork()前にmalloc()等で確保していた領域もそのまま使えるが、free()は親プロセス側と子プロセス側で別々に行う必要がある。
(ポインターの値を表示してみると、親プロセス側も子プロセス側も同じ値が表示される。しかしOSレベルでは別の領域が割り当てられているので、実際は被ってはいない。ポインターは、いわば“プロセスに割り当てられたメモリーの先頭アドレス”からの相対値に過ぎない)
fork()の戻り値は、-1ならエラー、0なら子プロセス起動成功、正の数は起動された子プロセスのプロセスIDを示す。
つまり、fork()が成功すると、正数が返って来た側はそれまでのプロセス(親プロセス)として処理を続行しているが、0が返って来た側は子プロセスとして別プロセスになっている(プログラム上は処理を続行しているように見える)。
なぜ子プロセスを生成する関数の名前が「fork」なのかというと、食事に出てくるナイフ・スプーン・フォークのフォークがイメージらしい。
ひとつのプログラムの流れがいくつかに分岐する図がフォークに似ているから?
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
int main() { int n = 123; pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(-1); } else if (pid == 0) { // 子プロセス printf("child n[%p]=%d\n", &n, n); n = 456; printf("child n[%p]=%d\n", &n, n); return 1; } else { // 親プロセス printf("parent n[%p]=%d\n", &n, n); n = 789; printf("parent n[%p]=%d\n", &n, n); return 2; } }
$ gcc fork1.c -o fork1.out $ ./fork1.out child n[0x7fff842a4498]=123 child n[0x7fff842a4498]=456 parent n[0x7fff842a4498]=123 parent n[0x7fff842a4498]=789 $ echo $? 2
表示されている変数のアドレス(ポインター)は親プロセスと子プロセスで同じだが、変数は当然共有されていない。
プログラムの戻り値($?)は親プロセスのものが返ってくる。
printf()の出力先(標準出力や標準エラー)は親プロセスと子プロセスで共有されているので、同じ場所に出力される。
(ファイルは共有されるということらしい)
子プロセスが終了するのを待つにはwaitpid()(あるいはwait())を使用する。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h>
int main() { pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(-1); } else if (pid == 0) { // 子プロセス printf("child process start\n"); sleep(1); // 1秒待つ printf("child process end\n"); return 12; //子プロセスの終了コードはWEXITSTATUS()で取れる } // 親プロセス printf("parent process start\n"); int status; pid_t r = waitpid(pid, &status, 0); //子プロセスのプロセスIDを指定して、終了を待つ if (r < 0) { perror("waitpid"); exit(-1); } if (WIFEXITED(status)) { // 子プロセスが正常終了の場合 int exit_code = WEXITSTATUS(status); //子プロセスの終了コード printf("child exit-code=%d\n", exit_code); } else { printf("child status=%04x\n", status); } printf("parent process end\n"); return 0; }
waitpid()に子プロセスのプロセスID(fork()の戻り値)を渡して、子プロセスの終了を待つ。
waitpid()が正常に終了すれば第2引数に渡したstatusに値が返ってくるので、それを判定する。
判定にはWIFEXITEDマクロ(Wait-IF-EXITED)を使用する。
WIFEXITED()が真の場合は子プロセスが正常終了したことを意味するので、WEXITSTATUSマクロ(Wait-EXIT-STATUS)を使って子プロセスの終了コードが取得できる。
ちなみに終了コードは8bitしか無いっぽい。
WIFEXITED以外にも、子プロセスがシグナルを受信して終了したかどうかを判定するWIFSIGNALED(Wait-IF-SIGNALED)など色々ある。
→JM ProjectのMan page of WAIT
$ gcc fork2.c -o fork2.out $ ./fork2.out child process start parent process start child process end child exit-code=12 parent process end