linuxのC言語でforkしたりpipeでおしゃべりしたり

linuxだとforkってやつをよく聞くけれど、実際どんなもんなんだろうと思って息抜きがてら試してみた。

unistd.hで定義されているforkって関数を呼ぶだけでプロセスを二つに分けられるらしい。 子プロセスには0が、親プロセスには子プロセスのpidが返るらしい。エラーだと-1らしい。

sys/wait.hwaitって関数で子プロセスが止まるのを待てるらしい。 waitpidを使えば特定の子プロセスを待てるらしい。 waitの引数はNULLかintへのポインタで、子プロセスの戻り値を受け取れるらしい。

プロセス間で通信するにはパイプを使うのが手っ取り早そうだ。 同じくunistd.hpipe関数でパイプを作れる。

戻り値は0なら成功、-1なら失敗。引数で渡したintが2つ分の配列に作ったパイプのファイルディスクリプタが入る。 0番目が読み込み、1番目が書き込み、のようだ。

作ったパイプはread/writeで読み書きできる。 ファイルディスクリプタなんでselectとかも使えると思う。試してないけど。

まあ細かいことは面倒くさいのでソースコード。

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

// 子プロセス
//  helloって送って、worldを受け取る。パイプを閉じて死ぬ。
//  パイプを作っているのが親(というかフォーク前)なのだから、後片付けはwait後にやった方が自然な気がするけれど、まあ良いか。
void child(static int r, static int w)
{
    char buf[1024];

    write(w, "hello", 6);
    printf("child> send> hello\n");

    while(read(r, buf, sizeof(buf)) <= 0);
    printf("child> recv> %s\n");

    close(r);
    close(w);
}

// 親プロセス
//  helloを受け取って、worldを送る。そしたらパイプを閉じて死ぬ。
void parent(static int r, static int w)
{
    char buf[1024];

    while(read(r, buf, sizeof(buf)) <= 0);
    printf("parent> recv>  %s\n", buf);

    write(w, "world", 6);
    printf("parent> send> world\n");

    close(r);
    close(w);
}

void main()
{
    int p2c[2], c2p[2];

    // パイプを作る。双方向通信したいので2セット作る。
    pipe(p2c);
    pipe(c2p);

    if(fork() == 0)  // ここでフォーク。子プロセスが一つなのでpidは無視して、親か子かだけ判定。
    {
        child(p2c[0], c2p[1]);
    }else
    {
        parent(c2p[0], p2c[1]);
        wait(NULL);
    }

    return 0;
}

それなりの長さのソースコードに見えて、プロセス管理はforkとwaitの行の二つしかない。 パイプもpipeで作ってcloseで閉じてるだけだ。めっちゃ簡単。

windowsのプロセス生成は何だか面倒くさかった覚えがあるので、この手軽さはかなり魅力的かも。 イメージ的にはまるっとコピーするforkは処理が重たそうだけれど、wikipedia曰くコピーオンライトらしいからそんなに重くないのかもしれない。

ちなみに、親が子を待たずに死んでもtopコマンドでは0 zombieのままだった。 調べてみたら、こういうプロセスのことを孤児プロセスと言うらしい。凄いそのまんまなネーミングだ。

自分を生んだ親が死んだらinitプロセスを里親にするらしい。その名もリペアレンティング。日本語で最育成だとさ。 initプロセスが親になるので、死ぬときはinitに看取ってもらえる。なのでゾンビにはならないそうな。

まあそんなわけで。何だかとっても適当な感じの記事になってしまった。


参考: