UNIXプログラミング入門~プロセス編(1/4)~

c/c++でunix系osプログラミングを行うときに使うunistd.hやその周辺のライブラリのお話その3です。
このメモでは、プロセス周りのシステムコールAPIに関して取り上げます。

プロセスとスレッド

プロセススレッドって何が違うの?

超簡単に表現すると同じメモリ資源を共有するかどうかです。

プロセス

それぞれが独立したメモリ資源、カーネル資源を与えられたプログラム。

スレッド

共通のメモリ資源、カーネル資源を扱うプログラム。

プロセスの親子関係

プロセスには通常、親子関係があります。
unix系osでは、カーネルの初期化後にinitプロセスと呼ばれるプロセスが起動します。
以降起動されるプロセスはinitをルートとするツリー構造で表現されるようになります。

pstree

コマンドを実行するとプロセスがどのような親子関係を持っているか確認できます。

マルチプロセスプログラミング

プロセスID

POSIXのプロセスには、正の整数でそれぞれにIDが割り振られている。
その番号をプロセスIDと呼びます。

プロセスIDはtopコマンドやpsコマンドで確認できます。

ps aux

ですべてのプロセス情報を表示できます。

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   8324   144 ?        Ss   16:40   0:00 /init ro
root         3  0.0  0.0   8332   152 tty1     Ss   16:40   0:00 /init ro
user1        4  0.0  0.0  15040  3480 tty1     S    16:40   0:00 -bash
user1       39  0.0  0.0  15664  1844 tty1     R    17:13   0:00 ps aux

子プロセスを生成する-fork(2)-

呼び出し元のプロセスを複製して子プロセスを生成するfork関数のプロトタイプはunistd.hに以下のように宣言されています。

pid_t fork(void);

forkの呼び出し以降の処理は二つのプロセスで実行されることに注意してください。

新しく生成された子プロセス側はforkの戻り値として0が返されます。
呼び出し元の親プロセス側には新しく生成された子プロセスのプロセスIDが返ります。

プロセスの生成に失敗した場合は親プロセス側に負の数が返ります。

例えば、以下のプログラムを実行してみいてください。

#include <unistd.h>
#include <stdio.h>
int main(void)
{
    pid_t pid = fork();
    
    printf("%d\n", pid);
    return 0;
}

数字が二回表示されるはずです。
0が表示されているのが子プロセス側です。

一般的には以下のように、条件分岐で親プロセス側と子プロセス側で動作を分けるような利用のされ方をします。

int main(void)
{
    pid_t pid = fork();
    if (pid < 0)
    {
        //例外処理
        return -1;
    }
    if (pid > 0)
    {
        //親プロセスにやらせたい処理
    }
    else
    {
        //子プロセスにやらせたい処理
    }
}

fork爆弾

察しが良い人は気づいたかもしれませんがforkを実行すると実行のたびにプロセスが増えます。
なので、以下のようなコードを間違って書いてしまうと大変なことになってしまいます。

int main(void)
{
    while(1)
    {
        fork();
    }
}

俗にfork爆弾と呼ばれネズミ算のように倍々にプロセスが増えていきシステムがダウンする恐れがあります。
軽い気持ちでは絶対に実行しないでください。

どれか一つの子プロセスの実行を待つ-wait(2)-

実行中のどれか一つの子プロセスの実行完了を待つ関数waitのプロトタイプは<sys/wait.h>に以下のように宣言されています。

pid_t wait(int *status);

第一引数には子プロセスの終了ステータス(※1)を格納するintのポインタを渡します。

戻り値は実行が完了した子プロセスのプロセスIDです。

※1 終了ステータス…main関数の戻り値 又は exit関数に渡した引数

任意の子プロセスの実行を待つ-waitpid(2)-

実行中の任意の子プロセスの実行完了を待つ関数waitpidのプロトタイプは<sys/wait.h>に以下のように宣言されています。

pid_t waitpid(pid_t pid, int *status, int options);

この関数は第三引数に渡す値によって挙動が変わります

ここでは第三引数に0を渡した場合のみ解説します。

第一引数には待機する子プロセスのプロセスIDを渡します。

第二引数には子プロセスの終了ステータスを格納するintのポインタを渡します。

第三引数には待機時のオプションを指定します。

戻り値は実行が完了した子プロセスのプロセスIDです。

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

int main(void)
{
    pid_t pid = fork();
    if(pid < 0)
    {
        printf("子プロセスの作成に失敗しました\n");
        return -1;
    }
    if(pid > 0)
    {
        int stat;
        pid_t p = waitpid(pid, &stat, 0);
        printf("計算結果は %d です\n", stat);
    }
    else
    {
        size_t i, j=0;
        for(i = 0; i < 1000; i++)
        {
            j += i;
        }
        printf("計算が終了しました\n");
        return j;
    }
    return 1;
}

このプログラムでは子プロセスで計算が終わるまで待機して計算結果を表示します。

出力

計算が終了しました
計算結果は 11264 です

最後に

次回、もう一種類関数を紹介して自作bash風プログラムを作成してみます。