プロセスの作成 fork
Unixでは、新しいプロセスを作成するために forkシステムコールを使用します。forkしたプロセスを親プロセス、forkで作成されたプロセスを子プロセスと呼びます。forkによりプロセスは、複製されます。親プロセスがfork前に開いていたファイルは、子プロセスも開いたまま、受け継がれます。
読み方
- fork
- ふぉーく
目次
概要
ここでは、Unix 系 OS (Linux や FreeBSD などの OS) でのプロセスの説明を行います。
ここでは、プロセスの作成や終了した子プロセスの処理について解説します。
プロセスの作成
Unixでは、新しいプロセスを作成するために forkシステムコールを使用します。forkしたプロセスを親プロセス、forkで作成されたプロセスを子プロセスと呼びます。forkによりプロセスは、複製されます。親プロセスがfork前に開いていたファイルは、子プロセスも開いたまま、受け継がれます。
終了した子プロセスの処理
一般的には、親プロセスは、子プロセスの終了を待ちます。子プロセスが終了したときには、終了ステータスなどの情報をOSが保存しています。親プロセスは、子プロセスの終了処理として、wait系システムコールでOSから終了ステータスを受け取ります。ステータスを受け取るまで、OSが保存し続けるため、waitでステータスを受け取らずにforkを繰り返すと、システムリソースを圧迫してしまいます。
シグナルで子プロセスの終了を知ることもできます。
waitシステムコールでステータスを受け取ったとき、プログラムの終了ステータスやどのように終了したのかを知ることができます。「プログラムの終了ステータス」は、exit()で渡された値です。どのように終了したか、というのは、コアダンプをしたのか、シグナルによって終了させられたのかがわかります。 WEXITSTATUSなどのマクロが用意されています。WIFEXITEDは、正常に終了したかを調べます。WEXITSTATUSは、exit系関数で渡された値を取得します。
プロセスの作成はどのように利用されるか?
プロセスの作成は、どういった時に利用されるのでしょうか?
それは、主に
- 新しいプログラムを実行したいとき
- 並列に処理を行いたいとき
- デーモンプロセスを作成したいとき
などに利用されます。
それぞれについて、具体的に解説をしましょう。
プログラムを実行する場合には、 exec 系システムコールが利用されます。 exec 系は、プログラムそのものを置き換えるシステムコールです。 シェルにコマンドを入力したときに、シェルはコマンドを実行します。 このとき、シェルは、 fork システムコールを呼び、シェルの子プロセスとして、コマンドを実行します。
シェルに、パイプを利用して、複数のコマンドを指定したときには、コマンドの数だけ fork を行い、それぞれのプロセスの入出力を dup/pipe のシステムコールを利用して、つなぎあわせて、コマンドを exec システムコールで実行します。
並列に処理を行うとき、というのは、ウェブサーバが複数のアクセスを同時に受け付けて処理したいケースなどです。
プログラムはどのようにでも書けますが、ウェブサーバを fork モデル(マルチプロセス)で作成する場合には、通信を受け付けたときに、子プロセスを作って、クライアントの相手は、子プロセスに任せる、といった書き方ができます。
Apache HTTPD と呼ばれるウェブサーバでは、fork モードでプロセスを複数待受の状態にして、http クライアントの通信を待つことができます。
並列に処理を行う場合には、プロセスではなく、スレッドを作成する方法もあり、詳しくは、pthread をご参照下さい。
デーモンプロセスを作成する場合にも fork() が利用されます。もちろん、 daemon システムコールを利用しても構いません。
一般的には、 fork システムコールを2回呼び出すことで、端末と切り離したプロセスを作成します。
forkの動き
fork システムコールを呼び出すと、fork の行からコピーされた子プロセスを含め、2つのプロセスの処理を実行します。
親プロセスと子プロセスの見分ける方法は、 fork システムコールの返した戻り値で決まります。
fork システムコールの戻り値は
- 親プロセス
- 0より大きい整数
- 子プロセス
- 0
になります。 forkに失敗したときは、 -1 が返されます。
そのため、 fork を実行したら、成功したのか、失敗したのかをチェックし、それ以降は、子プロセス(pid_t の値が 0 )なのか、そうでないかをチェックします。
forkファミリー
forkには、いくつかのシステムコールが存在します。
- fork
- vfork
- rfork
ヘッダファイル
fork
fork のプロトタイプは以下の通りです。
#include <unistd.h> pid_t fork(void);
wait
wait系システムコールのプロトタイプは以下の通りです。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t wpid, int *status, int options); #include <sys/signal.h> int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options); #include <sys/time.h> #include <sys/resource.h> pid_t wait3(int *status, int options, struct rusage *rusage); pid_t wait4(pid_t wpid, int *status, int options, struct rusage *rusage); pid_t wait6(idtype_t idtype, id_t id, int *status, int options, struct __wrusage *wrusage, siginfo_t *infop);
単純なforkの例
この例は、完全なプログラムとはいえません。
ソースコード fork1.c
このプログラムは、 fork をして、親プロセスと子プロセスでそれぞれ別のメッセージを出力して、終了するプログラムです。
forkが失敗した場合には、 -1 が返されます。 forkが失敗するということは、おそらく大抵の場合は、OSのリソースが枯渇していることを意味します。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> // fork #include <unistd.h> // fork #include <err.h> #include <errno.h> int main(int argc, char *argv[]) { pid_t pid; pid = fork (); if (-1 == pid) { err (EXIT_FAILURE, "can not fork"); } else if (0 == pid) { // child puts ("child"); exit(EXIT_SUCCESS); } else { // parent puts ("parents"); } exit (EXIT_SUCCESS); }
コンパイル
$ cc fork1.c
実行例
親プロセスと子プロセスで別々のメッセージを表示し、終了しています。
$ ./a.out parents child
単純なwaitの例
ここでは、子プロセスが終了したときの処理の例を紹介します。
wait.c
forkで子プロセスを作成し、親プロセスは、子プロセスの終了を wait システムコールで待ちます。 waitシステムコールの第1引数で受け取った、ステータスを表示します。
#include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <sys/types.h> // fork/wait #include <unistd.h> // fork/sleep #include <sys/wait.h> // fork/wait #include <err.h> #include <errno.h> int main(int argc, char *argv[]) { int status = 0; pid_t wait_pid; pid_t pid; pid = fork (); if (-1 == pid) { err (EXIT_FAILURE, "can not fork"); } else if (0 == pid) { // child (void) puts ("child start"); sleep (5); // 子プロセスの長い処理 (void) puts ("child end"); exit (EXIT_SUCCESS); /* NOTREACHED */ } // parent (void) printf ("parents, child is %d\n", pid); wait_pid = wait (& status); if (wait_pid == -1) { // wait が失敗した err (EXIT_FAILURE, "wait error"); } (void) printf ("child = %d, status=%d\n", wait_pid, status); exit (EXIT_SUCCESS); }
コンパイル
$ cc wait.c
実行例
$ ./a.out parents, child is 35975 child start child end child = 35975, status=0
wait系のマクロ
wait系のシステムコールで、ステータスが返されますが、上位ビットと下位ビットで、それぞれ意味を持っているため、マクロを利用して、ステータスをチェックするようになっています。
WIFCONTINUED(status) True if the process has not terminated, and has continued after a job control stop. This macro can be true only if the wait call specified the WCONTINUED option). WIFEXITED(status) True if the process terminated normally by a call to _exit(2) or exit(3). WIFSIGNALED(status) True if the process terminated due to receipt of a signal. WIFSTOPPED(status) True if the process has not terminated, but has stopped and can be restarted. This macro can be true only if the wait call spec‐ ified the WUNTRACED option or if the child process is being traced (see ptrace(2)). Depending on the values of those macros, the following macros produce the remaining status information about the child process: WEXITSTATUS(status) If WIFEXITED(status) is true, evaluates to the low‐order 8 bits of the argument passed to _exit(2) or exit(3) by the child. WTERMSIG(status) If WIFSIGNALED(status) is true, evaluates to the number of the signal that caused the termination of the process. WCOREDUMP(status) If WIFSIGNALED(status) is true, evaluates as true if the termina‐ tion of the process was accompanied by the creation of a core file containing an image of the process when the signal was received. WSTOPSIG(status) If WIFSTOPPED(status) is true, evaluates to the number of the signal that caused the process to stop.
関連項目
- プロセスの作成 fork
- 複数の子プロセスをforkする
- コマンドの実行 exec
- popenでコマンドの出力を読み込む
- C言語解説
ツイート