🪈

【C言語】pipe()とは 【関数解説】

に公開

みなさんこんにちは、ウタです。

先日、42tokyoのpipexの課題を完成させたのですが、その中で使用したpipe()関数についての理解度が足りないなと思ったので記事にしました。

まずはサンプルコード

関数の定義

int pipe(int pipefd[2]);

引数:

  • pipefd[2]:2つの整数配列(ファイルディスクリプタを格納)
    • pipefd[0]:読み取り用fd(read end)
    • pipefd[1]:書き込み用fd(write end)

返り値:

  • 成功時:0
  • 失敗時:-1

引数のpipefd[2]は、下記のようにポインター記法や配列記法(サイズ未指定)でも動作はします。

int pipe(int *pipefd);     // ポインター記法
int pipe(int pipefd[]);    // 配列記法(サイズ未指定)
int pipe(int pipefd[2]);   // 配列記法(サイズ指定)

しかし、実際のmanページでは:

  • Linux: int pipe(int pipefd[2]);
  • BSD系: int pipe(int pipefd[2]);

のように記載されています。
理由としては

  • 明確性:2つの要素が必要だとすぐ分かる
  • 可読性:パイプが[読み取り, 書き込み]の2つのfdを使うことが明確
  • 公式仕様:多くのmanページがこの形式

といったものが挙げられるかと思います。
ちなみに
int pipefd[10];を引数にしても

int pipefd[10];  // 10個の要素
pipe(pipefd);    // pipefd[0]とpipefd[1]のみ使用、残りは未初期化

となります。
では、複数のパイプが必要なときはどうするかというと、

int pipe1[2];
int pipe2[2];
pipe(pipe1);  // 1つ目のパイプ
pipe(pipe2);  // 2つ目のパイプ

こうすればいいわけです。

つまり:

  • 1つのpipe()呼び出し = 1つのパイプ = 2つのfd
  • インデックス2以降は絶対に使用されない
  • int pipefd[2]が必要十分なサイズ

だからこそ関数定義でもpipefd[2]と明記されています。

何をやっている関数なの?

pipe()は「データの通り道(パイプ)」を作る関数です。

身近な例で例えると:

  • 郵便ポスト:一方が手紙を投函、他方が回収
  • トイレットペーパーの芯:片側から話して、反対側で聞く
    などなど
int pipefd[2];
pipe(pipefd);  // パイプを作成し、pipefd[0]が読み取り用、pipefd[1]が書き込み用fdに設定される

// pipefd[1] ←書き込み口(入り口)
//     |
//   [筒の中]  ←データが流れる
//     |
// pipefd[0] ←読み取り口(出口)

サンプルコードで解説

では、実際のコードで動きを見ていきましょう。

1. fork()でプロセス分岐:

int main (void) {
    int pipefd[2];
    char buffer[100];
    pipe(pipefd);

    if (fork() == 0) {
        // 子プロセスの処理
        read(pipefd[0], buffer, sizeof(buffer));  // 筒から読み取り
    } else {
        // 親プロセスの処理  
        write(pipefd[1], "Hello", 5);    // 筒に書き込み
    }
}

*コードをシンプルにするためにfdclose()やエラーハンドリングなどは省略しております。🙇🏻

2. 親プロセスの動作:

write(pipefd[1], "Hello", 5);
  • pipefd[1](書き込み用fd)に"Hello"を送信
  • データはパイプ内部のバッファに格納される

3. 子プロセスの動作:

read(pipefd[0], buffer, size);
  • pipefd[0](読み取り用fd)からデータを受信
  • 親が送った"Hello"がbufferに格納される

視覚的な流れ

重要なポイント:

  • 同じパイプを共有:fork()[1]後も親子が同じpipefd[2]を持つ
  • データの流れ:親の書き込み → パイプ → 子の読み取り
  • ブロッキング:子のread()は親がwrite()するまで待機

こちらのサンプルコードでは、まずpipe(pipefd);でカーネルとのパイプを確保します。
続いて、fork()で分岐した親プロセスのwrite() によってデータ送信が行われます。
送信されたデータはパイプを通ってread()で受け取られます。最終的に子プロセスのbufferに"Hello"が格納されます。

いつ使うのか?

じゃあ、この関数をいつ使うのかというと基本的にfork()とセットで使われることが多いです。
理由としては、pipe()はプロセス間通信のための関数でありシングルプロセスでは使われることがほとんどないためです。逆にfork()を使用して親プロセスと子プロセスに別れた際にプロセス間でデータ通信を行うためには必須となってくる関数です。なので、42tokyoのpipexの課題ではfork()pipe()の両方の理解が必要となってきます。

まとめ

この記事では、pipe()について以下のポイントを説明しました:

  • pipe()はプロセス間通信のための「筒」を作る関数
  • 引数は必ずint pipefd[2]:読み取り用と書き込み用の2つのfd
  • 一方向通信pipefd[1](書き込み)→ pipefd[0](読み取り)
  • fork()とセットで使用:親子プロセス間でのデータ受け渡しが主な用途
  • ブロッキング動作:データがない場合、read()は待機状態になる

pipe()は単体では意味をなさず、fork()と組み合わせて使う関数です。Unix系システムの「すべてをファイルとして扱う」哲学の表れでもあり、シンプルな仕組みで強力なプロセス間通信を実現しています。

pipexの課題では、このpipe()fork()を組み合わせることで、シェルの|(パイプ)機能を実装していくことになります。次回は、これらと合わせて重要なdup2()について解説していきます!

脚注
  1. fork()ついては【C言語】fork()とは 【関数解説】
    という記事で関数解説していますので、よかったら見ていってください! ↩︎

Discussion