IPC: fork() システムコール
fork はシステムコールで、新しいプロセスを生成する際に使用します。
SYNOPSIS
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
DESCRIPTION
fork()
システムコールは、元となるプロセス(親プロセス)を複製し PID (プロセス ID)と PPID (親プロセス ID) が 異なるプロセス(子プロセス)を生成します。新しく作られた子プロセスは、親プロセスのファイルロックと保留しているシグナル以外ほとんどがコピーされます。
fork()
システムコールが呼び出された後、親プロセスには子プロセスのプロセスIDが返され、子プロセスには 0
が返されます。fork()
システムコールが失敗した場合、親プロセスに -1
が返され子プロセスは生成されません。
親プロセスは、子プロセスを生成した後は通常の処理に戻ります。子プロセスもまた、親プロセスとは別に自分のサービスが終わるまで存在します(exit()
システムコールが呼ばれるまで)。
fork()
システムコールを使用する場合には注意が必要です。たとえば、fork を無限に呼び出すようなプログラムを作ってしまった場合、システムのプロセステーブルを使いきってしまいシステム管理者にひどく叱られる事になるので注意してください。
UNIXのプロセスは、終了すると一旦ゾンビプロセス<defunct>
となり、そのプロセスの親プロセスが wait()
システムコールを呼び出すまで存在しつづけます。親プロセスが wait()
システムコールを呼び出すと、ゾンビプロセスの終了時の終了ステータスが親プロセスに返され、ゾンビプロセスが消滅します。ゾンビプロセスは、子プロセスの終了ステータスを親プロセスに伝える仕組みとして存在しています。
親プロセスが wait()
システムコールを呼び出したとき、その子プロセスが既に終了している場合はすぐに終了ステータスを返します。しかし、どの子プロセスも終了していない場合、wait()
システムコールを呼び出した親プロセスは、いずれかの子プロセスが終了するまでずっと待った状態になり、親プロセスが処理したい次の処理が出来ないままになってしまいます。子プロセスの終了タイミングは、親プロセスには予測が不可能であることが多いため、親プロセスが wait()
システムコールを呼び出すべきタイミングは非常に難しくなります。これを補うために、子プロセスが終了したタイミングで親プロセスに対して送られるSIGCHLD
シグナルというものがあります。
シグナルは、ソフトウェアの割り込み機構で非同期処理の実装に利用されます。あるプロセスがシグナルを受信すると、そのプロセスが現在実行中の処理を一時中断し、そのシグナルに対応したシグナルハンドラを実行します。シグナルハンドラの処理が終了すると、中断していた処理を再開します。シグナルハンドラとは、プロセスがシグナルを受信したときに実行する関数のことです。
この SIGCHLD
シグナルに割り当てるシグナルハンドラにおいて wait()
システムコールを呼び出すことで、子プロセスが終了したタイミングで親プロセスが wait()
システムコールを呼び出すことができるようになります。
しかし、子プロセスが複数ある時に同時に SIGCHLD
シグナルが発生した場合、シグナルの仕様でシグナルハンドラは1回しか実行されません。したがって、ゾンビプロセスの個数に応じて wait()
システムコールを複数回呼び出さなくてはなりません。しかし、親プロセスにはゾンビプロセスの個数を知る手段がないため wait()
システムコールを何回呼び出せばよいのかわかりません。wait()
システムコールを余分に呼び出してしまうと、親プロセスは子プロセスの終了を待ってしまうため、シグナルハンドラ内で親プロセスの処理が停止してしまいます。
そこで、こうした問題に対応できるように waitpid()
システムコールが作られました。waitpid()
システムコールは、wait()
システムコールを拡張したものです。waitpid()
システムコールの三番目の引数に WNOHANG
を指定することにより、プロセスが不用意に中断しないようになります。waitpid()
システムコールは、ゾンビプロセスを処理した場合にゾンビプロセスのプロセスIDを返し、ゾンビプロセスがいない場合は 0
を返します。このことにより、SIGCHLD
シグナルのシグナルハンドラで waitpid()
システムコールの返り値が 0
になるまで繰り返すことですべてのゾンビプロセスを消滅させることができます。
SAMPLE
fork()
システムコールの戻り値は、親プロセスには子プロセスのプロセスIDが返され、子プロセスには 0
が返されます。失敗した場合、親プロセスに -1
が返され子プロセスは生成されません。
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid; int cstat; switch(pid=fork()) { case -1: perror("fork"); /* something wrong */ exit(1); /* parent exits */ case 0: printf(" CHILD: PID is %d\n", getpid()); printf(" CHILD: Parent's PID is %d\n", getppid()); sleep(1); printf(" CHILD: Please enter exit status: "); scanf("%d", &cstat); printf(" CHILD: exit!\n"); exit(cstat); default: printf("PARENT: PID is %d\n", getpid()); printf("PARENT: Child's PID is %d\n", pid); printf("PARENT: Waiting for child to exit()...\n"); wait(&cstat); printf("PARENT: Child's exit status is: %d\n", WEXITSTATUS(cstat)); printf("PARENT: exit!\n"); } return 0; }
これをコンパイルし実行すると以下のようになります。
# gcc fork_sample.c -o fork_sample # ./fork_sample CHILD: PID is 1225 CHILD: Parent's PID is 1224 PARENT: PID is 1224 PARENT: Child's PID is 1225 PARENT: Waiting for child to exit()... CHILD: Please enter exit status: 12 CHILD: exit! PARENT: Child's exit status is: 12 PARENT: exit! #
シグナルを用いた子プロセスの終了処理の例(必要な部分のみ記述)
int main() { ... struct sigaction act_child; act_child.sa_handler = SIGCHLD_handler; sigemptyset(&act_child.sa_mask); act_child.sa_flags = SA_NOCLDSTOP | SA_RESTART; sigaction(SIGCHLD, &act_child, NULL); switch(pid=fork()) { case -1: perror("fork"); /* something wrong */ exit(1); /* parent exits */ case 0: child_process(); default: parent_process(); } ... } void SIGCHLD_handler(int sig_no) { pid_t pid_child = 0; do { int ret_child; pid_child = waitpid(-1, &ret_child, WNOHANG); } while(pid_child > 0); }
SEE ALSO
fork(2), exit(2), wait(2), waidpid(2)