プロセスの作成 fork

提供: C言語入門
移動: 案内検索
スポンサーリンク

Unixでは、新しいプロセスを作成するために forkシステムコールを使用します。forkしたプロセスを親プロセス、forkで作成されたプロセスを子プロセスと呼びます。forkによりプロセスは、複製されます。親プロセスがfork前に開いていたファイルは、子プロセスも開いたまま、受け継がれます。

読み方

fork
ふぉーく

概要

ここでは、UnixOS (LinuxFreeBSD などの 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.

関連項目




スポンサーリンク