プロセスの生成とfork

このエントリーをはてなブックマークに追加

プロセスとは?

ここではUnixで使われているプロセス生成を行うforkやプロセスの一生では説明しきれなかった部分について解説を行っていきます。

プロセスの観察

psコマンド

まずは現在システム上で動作しているプロセスをターミナルから観察してみましょう。

# linuxの場合
$ ps aux

# BSD系の場合
$ ps -ef

出力は省略しますが、結果を見ると様々なプロセスが起動していることが分かります。
1行が1つのプロセスに対応しています。
今回注目したいのはPIDです。これはプロセスIDといいプロセス毎にユニークなIDが割り振られています。
PIDはOSがプロセスを管理するために使われます。マルチプログラミングを行う上でも重要です。


pstreeコマンド

つぎに、pstreeコマンドを紹介します。
pstreeはLinuxには存在しますが、システムによっては存在しないかもしれません。
psはプロセスのリストを表示するコマンドでしたが、pstreeはプロセスをtree上に表示することが可能です。

$ pstree -p

このコマンドは非常に重要な事を表示しています。それはプロセスの親子関係です。
プロセスはプロセスを起動することが可能であり、起動した側を親プロセス、起動された側を子プロセスといいます。
親プロセスと子プロセスは別々に動作し、メモリも異なるために同時に複数の処理を行いたい場合等に使用します。


プロセスの生成とPID

プロセスID(PID)

まずはプロセスIDを取得する方法です。
プロセスIDを取得するには、getpid関数を使用します。プロトタイプは以下の通りです。

#include <sys/types.h> //システムによっては不必要
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void); 

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
        pid_t c_pid;
        pid_t p_pid;

        c_pid = getpid();
        p_pid = getppid();

        //現在のプロセスID
        printf("PID:%d\n", c_pid);

        //親のプロセスID
        printf("PPID:%d\n", p_pid);

        return 0;
}

実行するとPIDが表示されています。
$ gcc -o pid pid.c
$ ./pid
PID:31209
PPID:28548

getpid()関数は常に成功し、現在のプロセスのPIDを返します。
getppid()関数は常に成功し、現在のプロセスの親のPIDを返します。
また、pid_tは実際にはint、long等の整数型のtypedefとなっている事通常ですので、printfでは%dで表示が可能です。

さて、現在のプロセスは自明ですが、問題は親プロセスです。
この場合の親プロセスはないになるでしょうか?
$ ps aux|grep 28548
khondalit          28548   0.0  0.0  2444636   1356 s003  S     6:37PM   0:00.05 bash

shellから起動したので、親プロセスは当然ながらbashのようです。
ここの部分が次のforkで重要になってきます。


fork

次にforkで子プロセスを作成する方法を学びます
プロトタイプは以下の通りです。

#include <unistd.h>
pid_t fork(void);

manの返り値の項目に注目してください。
成功した場合、親プロセスには子プロセスの PID が返され、 子プロセスには 0 が返される。
失敗した場合、親プロセスに -1 が返され、子プロセスは生成されず、 errno が適切に設定される。

非常に分かり辛いのですが、これを理解した上でサンプルプログラムを見てみましょう。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

int main(void)
{
        pid_t pid;

        printf("root getpid:%d\n", getpid());
        printf("root getppid:%d\n", getppid());

        pid = fork();
        if (pid < 0) {
                fprintf(stderr, "Error fork %d\n", errno);
                exit(EXIT_FAILURE);
        }
        else if (pid == 0) {
                //子プロセス処理
                printf("child pid:%d\n", pid);
                printf("child getpid:%d\n", getpid());
                printf("child getppid:%d\n", getppid());
                _exit(0); //子プロセスでは終了時には_exitを呼ぶことに注意!
        }
        else {
                //親プロセス処理
                printf("parent pid:%d\n", pid);
                printf("parent getpid:%d\n", getpid());
                printf("parent getppid:%d\n", getppid());
                waitpid(pid, NULL, 0); //子プロセス終了を待つ
                //kill(pid, SIGKILL); //子プロセスにシグナルを送信する
                exit(0); //親プロセスでは終了時にはexitを呼ぶ
        }

        return 0;
}


コードの解説の前に動作結果も見てください。
$ gcc -o fork fork.c
$ ./fork
root getpid:31640
root getppid:28548
parent pid:31641
parent getpid:31640
parent getppid:28548
child pid:0
child getpid:31641
child getppid:31640

通常の関数と異なり、forkは1回しか呼ばれませんが親プロセスと子プロセスで2回戻ります。
つまり親プロセスと子プロセスの処理を同じコード上に書く事になります。

parentのpidは子プロセスのPIDなので子プロセスのgetpidと同じになっています。
これはwait関数やkill関数を呼ぶ等子プロセスを親プロセスからコントロールする為にこのようになっています。

子プロセスの終了には_exitを使用している事に注意してください。
これはexit関数が行う終了処理の違いの為です。exit関数を呼ぶのは1つのmain関数につき1つにすべきでしょう。


まとめ

ここではPIDとforkについて紹介しました。
次ではexec関数群の紹介をします。