サーチ…


前書き

パイプは、単一のマシンの範囲内で一方向のプロセス間またはスレッド間の通信のためのメカニズムです。論理的には、パイプは2つの接続された終端で構成され、一方はデータを書き込むことができ、もう一方はそのデータを後で読み取ることができ、書き込みと読み取りの間にデータバッファを同期させる必要はありません。パイプは、パイプの適用であるシェルパイプラインと区別する必要があります。

基本的な作成と使用

匿名パイプ(または単にパイプ)は、プロセスにファイルディスクリプタのペアとして公開されるカーネル管理オブジェクトです.1つは読み取り終端用で、もう1つは書き込み終端用です。それらはpipe (2)関数によって作成されます:

int pipefds[2];
int result;

result = pipe(pipefds);

成功した場合、 pipe()は、 pipe()の読み込み側の記述子を、指定された配列のインデックス0に、書き込み側の記述子をインデックス1に記録します。これらのインデックスは、標準ストリームの従来のファイル記述子番号に類似しています。

パイプを作成したら、POSIX I / O関数を使用して書き込み側に書き込むか、読み取り側から読み取ります。

プロセス1:

ssize_t bytes_written = write(pipefds[1], "Hello, World!", 14);

プロセス2:

char buffer[256];
ssize_t bytes_read = read(pipefds[0], buffer, sizeof(buffer));

代わりに、 fdopen()を使用して、Cのstdio関数で使用するFILE構造体の両方のパイプ端をラップすることができます。

FILE *write_end = fdopen(pipefds[1]);

if (write_end) {
    fputs("Hello, World!");
}

パイプには有限のI / Oバッファがあり、フルバッファを持つパイプへの通常の書き込みはブロックされます。したがって、パイプは、パイプに書き込むスレッドだけがそれから読み取るスレッドであるかのように、スレッドがデッドロックされたブロックをブロックするとすぐに、スレッドが自身と通信するための安全なメカニズムではありません。

いずれかのプロセスがいずれかのパイプ端のファイル記述を開いている限り、パイプは存続します。 close (2)関数は、ファイルディスクリプタによって表されるパイプエンドを閉じるために使用され、 fclose (3)は、 FILEを介してパイプエンドを閉じるために使用されます。

子プロセスへのパイプの確立

ファイル記述子とFILEオブジェクトは、通常のI / Oを介してプロセス間で交換できないプロセス単位のリソースです。したがって、2つの別個のプロセスが匿名パイプを介して通信するためには、参加するプロセスの一方または両方が、親プロセスまたは遠い祖先でなければならないパイプを作成したプロセスからオープンパイプエンドを継承する必要があります。最も単純なケースは、親プロセスが子プロセスと通信したい場合です。

子プロセスは親から必要なオープンファイル記述を継承する必要があるため、パイプを最初に作成する必要があります。その後、親はフォークします。通常、各プロセスは厳密には読者または厳密にはライターになります。その場合、使用するつもりのないパイプの端をそれぞれ閉じなければなりません。

void demo() {
    int pipefds[2];
    pid_t pid;

    // Create the pipe
    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (pid = fork()) {
        case -1:
            // error - abort ...
            break;
        case 0:   /* child */
            close(pipefds[0]);
            write(pipefds[1], "Goodbye, and thanks for all the fish!", 37);
            exit(0);
        default:  /* parent */
            close(pipefds[1]);

            char buffer[256];
            ssize_t nread = read(pipefds[0], sizeof(buffer) - 1);

            if (nread >= 0) {
                buffer[nread] = '\0';
                printf("My child said '%s'\n", buffer);
            }

            // collect the child
            wait(NULL);

            break;
    }
}

2つの子プロセスをパイプ経由で接続する

パイプを介して2つの子プロセスを接続するには、2つの子のそれぞれを、同じパイプの異なる端を介して親に接続します。通常、親は子どもの会話のパーティではないので、両方のパイプの端のコピーを閉じます。

int demo() {
    int pipefds[2];
    pid_t child1, child2;

    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            write(pipefds[1], "Hello, brother!", 15);
            exit(0);
        default:  /* parent */
            // nothing
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 2 */
            char buffer[256];
            ssize_t nread;

            close(pipefds[1]);
            nread = read(pipefds[0], buffer, sizeof(buffer) - 1);
            if (nread < 0) {
                // handle error
            } else {
                buffer[nread] = '\0';
                printf("My brother told me '%s'\n", buffer);
            }
            exit(0);
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    if (child1 >= 0) {
        wait(NULL);
        if (child2 >= 0) {
            wait(NULL);
        }
    }
}

シェルスタイルのパイプラインを作成する

シェルスタイルのパイプラインは、2つ以上のプロセスで構成され、それぞれが標準出力を次の標準入力に接続します。出力から入力への接続はパイプ上に構築されています。パイプライン型のプロセスセットを確立するには、別の例で説明したようにパイプ接続された子プロセスを作成し、 dup2 (2)関数を使用して各パイプエンドをプロセスの適切な標準ファイル記述子に複製します。一般的には、オリジナルのパイプエンドファイル記述子を閉じることは良い考えです。特に、多くの場合そうであるように、子プロセスが後で別のコマンドを実行することを意図している場合は特にそうです。

// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
    int pipefds[2];
    pid_t child1 = -1, child2 = -1;

    pipe(pipefds);

    switch (child1 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            dup2(pipefds[1], STDOUT_FILENO);
            close(pipefds[1]);
            execl("/bin/cat", "cat", "/etc/motd", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    switch (child2 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 2 */
            close(pipefds[1]);
            dup2(pipefds[0], STDIN_FILENO);
            close(pipefds[0]);
            execl("/bin/grep", "grep", "[Ww]ombat", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    wait(NULL);
    wait(NULL);
}

パイプラインを設定する際のより一般的なエラーの1つは、使用していないパイプの終点に1つ以上のプロセスを関連付けることを忘れることです。経験則として、最終的な結果は各パイプ端がちょうど1つのプロセスによって所有され、そのプロセスでのみ開いていることです。

上記のデモ機能のようなケースでは、2番目の子または親のpipefds[1]を閉じるのに失敗すると、2番目の子がハングします。これは、 grepはEOFを見るまで入力を待機し続けます。親プロセスや2番目の子自体のようなプロセスでパイプの書き込み終了が開いたままである限り、観察されます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow