9

独立したスレッドとして実行され、パイプと一緒にフックできる個別のプログラム モジュールを作成することに興味があります。動機は、各モジュールを完全に独立して作成およびテストできること、おそらくそれらを異なる言語で記述したり、異なるモジュールを異なるマシンで実行したりできることです。ここにはさまざまな可能性があります。私はしばらく配管を使用しましたが、その動作のニュアンスに慣れていません。

  • 受信側は入力を待ってブロックするようですが、送信側は誰かがストリームから読み取るのを待っていることがありますか?
  • ストリームに eof を書き込んだ場合、ストリームを閉じるまでそのストリームへの書き込みを続けることはできますか?
  • 名前付きパイプと名前なしパイプの動作に違いはありますか?
  • 名前付きパイプで最初に開いたパイプの端は重要ですか?
  • 異なる Linux システム間でパイプの動作は一貫していますか?
  • パイプの動作は、使用しているシェルまたは構成方法によって異なりますか?
  • この方法でパイプを使用したい場合、他に尋ねるべき質問や知っておくべき問題はありますか?
4

3 に答える 3

4

うわー、それはたくさんの質問です。私がすべてをカバーできるかどうか見てみましょう...

受信側が入力の待機をブロックするようですが、これは予想どおりです

実際の「読み取り」呼び出しは、何かが存在するまでブロックされることを正しく期待しています。ただし、パイプで待機しているもの(およびその量)を「覗く」ことができるC関数がいくつかあると思います。残念ながら、これもブロックされるかどうかは覚えていません。

送信側のブロックは、誰かがストリームから読み取るのを時々待っていますか

いいえ、送信をブロックしないでください。これがネットワークを介して別のコンピューターへのパイプであった場合の影響について考えてみてください。他のコンピューターがそれを受信したことを応答するのを(おそらく高い待ち時間で)待ちますか?これは、宛先のリーダーハンドルが閉じられている場合の別のケースです。この場合、それを処理するためにエラーチェックが必要です。

ストリームにeofを書き込む場合、ストリームを閉じるまでそのストリームに書き込みを続けることができますか?

これは、使用している言語とそのパイプの実装によって異なると思います。Cでは、私はノーと言うでしょう。Linuxシェルでは、そうだと思います。より多くの経験を持つ他の誰かがそれに答えなければならないでしょう。

名前付きパイプと名前なしパイプの動作に違いはありますか?私の知る限り、そうです。ただし、名前付きと名前なしの経験はあまりありません。違いは次のとおりです。

  • 単方向通信と双方向通信
  • スレッドの「イン」ストリームと「アウト」ストリームの読み取りと書き込み

名前付きパイプで最初に開くパイプの端は重要ですか?

通常はありませんが、スレッドを作成して相互にリンクしようとすると、初期化で問題が発生する可能性があります。すべてのサブスレッドを作成し、それぞれのパイプを相互に同期する1つのメインスレッドが必要です。

異なるLinuxシステム間でパイプの動作は一貫していますか?

繰り返しますが、これはどの言語に依存しますが、一般的にはそうです。POSIXについて聞いたことがありますか?これが標準です(少なくともLinuxの場合、Windowsは独自の機能を備えています)。

パイプの動作は、使用しているシェルまたは構成方法によって異なりますか?

これはもう少し灰色の領域に入っています。シェルは基本的にシステムコールを行う必要があるため、答えはノーです。ただし、それまでのすべてが手に入る。

私が尋ねるべき他の質問はありますか

あなたが尋ねた質問は、あなたがシステムをきちんと理解していることを示しています。調査を続け、作業するレベル(シェル、Cなど)に焦点を合わせます。試してみるだけで、さらに多くのことを学ぶことができます。

于 2008-12-12T17:11:19.330 に答える
4

これはすべてUNIXライクなシステムに基づいています。最近のバージョンのWindowsの特定の動作に精通していません。

受信側は入力の待機をブロックするようですが、送信側は誰かがストリームから読み取るのを待機することがありますか?

はい、最近のマシンでは頻繁に発生しない場合がありますが。パイプには、いっぱいになる可能性のある中間バッファーがあります。含まれている場合、パイプの書き込み側は実際にブロックされます。しかし、考えてみれば、これを危険にさらすほどの大きさのファイルは多くありません。

ストリームにeofを書き込んだ場合、ストリームを閉じるまでそのストリームに書き込みを続けることはできますか?

ええと、あなたはCTRL-D、0x04のような意味ですか?確かに、ストリームがそのように設定されている限り。Viz。

506 # cat | od -c
abc
^D
efg
0000000    a   b   c  \n 004  \n   e   f   g  \n                        
0000012

名前付きパイプと名前なしパイプの動作に違いはありますか?

はい。ただし、それらは微妙で実装に依存します。最大の問題は、もう一方の端が実行される前に名前付きパイプに書き込むことができることです。名前のないパイプでは、ファイル記述子はfork / execプロセス中に共有されるため、プロセスが稼働していないと一時バッファーにアクセスする方法はありません。

名前付きパイプで最初に開くパイプの端は重要ですか?

いいえ。

異なるLinuxシステム間でパイプの動作は一貫していますか?

当然のことながら、そうです。バッファサイズなどは異なる場合があります。

パイプの動作は、使用しているシェルまたは構成方法によって異なりますか?

いいえ。パイプを作成すると、内部で親プロセス(シェル)がファイル記述子のペアを持つパイプを作成し、次の擬似コードのようなforkexecを実行します。

create pipe, returning two file descriptors, call them fd[0] and fd[1]
fork write-side process
fork read-side process

書き込み側

close fd[0]
connect fd[1] to stdout
exec writer program

読み取り側

close fd[1]
connect fd[0] to stdin
exec reader program

このようにパイプを使用したい場合、他に質問したり、知っておくべき問題はありますか?

あなたがやりたいことは本当にこのように一列に並んでいるのでしょうか?そうでない場合は、より一般的なアーキテクチャについて検討することをお勧めします。しかし、パイプの「狭い」インターフェースを介して相互作用する多数の個別のプロセスを持つことが望ましいという洞察は良いものです。

[更新:最初はファイル記述子のインデックスを逆にしました。それらは今正しいです、参照してくださいman 2 pipe。]

于 2008-12-12T17:16:19.790 に答える
4

Dashogun と Charlie Martin が指摘したように、これは大きな問題です。彼らの回答には不正確な部分があるので、私も回答します。

独立したスレッドとして実行され、パイプと一緒にフックできる個別のプログラム モジュールを作成することに興味があります。

単一プロセスのスレッド間の通信メカニズムとしてパイプを使用しようとすることには注意してください。1 つのプロセスでパイプの読み取り側と書き込み側の両方が開かれるため、EOF (ゼロ バイト) の表示は決して得られません。

本当にプロセスについて言及しているのであれば、これはツールを構築するための古典的な Unix アプローチの基礎です。標準の Unix プログラムの多くは、標準入力から読み取り、何らかの方法で変換し、結果を標準出力に書き込むフィルターです。たとえば、trsortgrep、およびcatはすべてフィルターです。これは、操作しているデータが許す場合に従うべき優れたパラダイムです。すべてのデータ操作がこのアプローチを助長するわけではありませんが、多くのデータ操作が助長されます。

動機は、各モジュールを完全に独立して記述およびテストできること、おそらくそれらを異なる言語で記述したり、異なるマシンで異なるモジュールを実行したりできることです。

良い点。rshor (better)などのプログラムを使用してそれに近づけることはできますが、実際にはマシン間にパイプ メカニズムがないことに注意してくださいssh。ただし、内部的には、そのようなプログラムはパイプからローカル データを読み取り、そのデータをリモート マシンに送信する場合がありますが、パイプを使用せずにソケットを介してマシン間で通信します。

ここにはさまざまな可能性があります。私はしばらく配管を使用しましたが、その動作のニュアンスに慣れていません。

わかった; 質問をすることは、学ぶための (良い) 方法の 1 つです。もちろん、実験は別のことです。

受信側は入力を待ってブロックするようですが、送信側は誰かがストリームから読み取るのを待っていることがありますか?

はい。パイプ バッファのサイズには制限があります。古典的には、これは非常に小さく、4096 または 5120 が一般的な値でした。最新の Linux ではより大きな値が使用されていることに気付くかもしれません。および _PC_PIPE_BUF を使用fpathconf()して、パイプ バッファーのサイズを調べることができます。POSIX では、バッファが 512 である必要があるだけです (つまり、_POSIX_PIPE_BUF は 512 です)。

ストリームに eof を書き込んだ場合、ストリームを閉じるまでそのストリームへの書き込みを続けることはできますか?

技術的には、EOF をストリームに書き込む方法はありません。EOF を示すためにパイプ記述子を閉じます。control-D または control-Z を EOF 文字と考えている場合、パイプに関する限り、それらは単なる通常の文字です。正規モード (cooked 、または通常)。

名前付きパイプと名前なしパイプの動作に違いはありますか?

はいといいえ。最大の違いは、名前のないパイプは 1 つのプロセスでセットアップする必要があり、そのプロセスと、そのプロセスを共通の祖先として共有する子によってのみ使用できることです。対照的に、名前付きパイプは、以前に関連付けられていないプロセスで使用できます。次の大きな違いは、最初の結果です。名前のないパイプを使用すると、 への単一の関数 (システム) 呼び出しから 2 つのファイル記述子pipe()が返されますが、通常の関数を使用して FIFO または名前付きパイプを開きopen()ます。(開く前に、誰かがmkfifo()呼び出しで FIFO を作成する必要があります。名前のないパイプには、そのような事前の設定は必要ありません。)ただし、ファイル記述子を開くと、名前付きパイプと名前のないパイプの間にほとんど違いはありません。

名前付きパイプで最初に開いたパイプの端は重要ですか?

いいえ。FIFO を開く最初のプロセスは、もう一方の端が開いているプロセスが存在するまで (通常は) ブロックされます。読み書き用に開いた場合 (通常ではありますが可能です)、ブロックされることはありません。O_NONBLOCK フラグを使用すると、ブロックされません。

異なる Linux システム間でパイプの動作は一貫していますか?

はい。パイプを使用したどのシステムでも、パイプに関する問題は聞いたことも経験したこともありません。

パイプの動作は、使用しているシェルまたは構成方法によって異なりますか?

いいえ: パイプと FIFO は、使用するシェルから独立しています。

この方法でパイプを使用したい場合、他に尋ねるべき質問や知っておくべき問題はありますか?

書き込みを行うプロセスではパイプの読み取り側を閉じ、読み取りを行うプロセスではパイプの書き込み側を閉じる必要があることを覚えておいてください。パイプを介した双方向通信が必要な場合は、2 つの別個のパイプを使用します。複雑な配管配置を作成する場合は、デッドロックに注意してください - 可能性があります。ただし、線形パイプラインはデッドロックしません (ただし、最初のプロセスが出力を閉じない場合、下流のプロセスは無期限に待機する可能性があります)。


上記と他の回答へのコメントの両方で、パイプバッファーは古典的に非常に小さなサイズに制限されていることを観察しました。@Charlie Martin は、Unix の一部のバージョンには動的パイプ バッファがあり、これらは非常に大きくなる可能性があると反論しました。

彼がどちらのことを考えているかわかりません。Solaris、AIX、HP-UX、MacOS X、Linux、および Cygwin / Windows XP で以下のテスト プログラムを使用しました (結果は以下)。

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static const char *arg0;

static void err_syserr(char *str)
{
    int errnum = errno;
    fprintf(stderr, "%s: %s - (%d) %s\n", arg0, str, errnum, strerror(errnum));
    exit(1);
}

int main(int argc, char **argv)
{
    int pd[2];
    pid_t kid;
    size_t i = 0;
    char buffer[2] = "a";
    int flags;

    arg0 = argv[0];

    if (pipe(pd) != 0)
        err_syserr("pipe() failed");
    if ((kid = fork()) < 0)
        err_syserr("fork() failed");
    else if (kid == 0)
    {
        close(pd[1]);
        pause();
    }
    /* else */
    close(pd[0]);
    if (fcntl(pd[1], F_GETFL, &flags) == -1)
        err_syserr("fcntl(F_GETFL) failed");
    flags |= O_NONBLOCK;
    if (fcntl(pd[1], F_SETFL, &flags) == -1)
        err_syserr("fcntl(F_SETFL) failed");
    while (write(pd[1], buffer, sizeof(buffer)-1) == sizeof(buffer)-1)
    {
        putchar('.');
        if (++i % 50 ==  0)
            printf("%u\n", (unsigned)i);
    }
    if (i % 50 !=  0)
        printf("%u\n", (unsigned)i);
    kill(kid, SIGINT);
    return 0;
}

他のプラットフォームから追加の結果を得たいと思っています。これが私が見つけたサイズです。すべての結果が予想よりも大きかったことは告白しなければなりませんが、Charlie と私はバッファー サイズに関して「かなり大きい」という意味について議論している可能性があります。

  •   8196 - IA-64 用の HP-UX 11.23 (fcntl(F_SETFL) が失敗しました)
  • 16384 - ソラリス 10
  • 16384 - MacOS X 10.5 (O_NONBLOCK は機能しませんでしたが、fcntl(F_SETFL) は失敗しませんでした)
  • 32768 - AIX 5.3
  • 65536 - Cygwin / Windows XP (O_NONBLOCK は機能しませんでしたが、fcntl(F_SETFL) は失敗しませんでした)
  • 65536 - SuSE Linux 10 (および CentOS) (fcntl(F_SETFL) が失敗しました)

これらのテストから明らかな点の 1 つは、O_NONBLOCK がパイプで機能するプラットフォームとそうでないプラットフォームがあることです。

プログラムは、パイプとフォークを作成します。子プロセスは、パイプの書き込み側を閉じてから、シグナルを受け取るまでスリープ状態になります。これが、pause() が行うことです。次に、親はパイプの読み取り側を閉じ、完全なパイプに書き込もうとしてもブロックされないように、書き込み記述子にフラグを設定します。次にループし、一度に 1 文字ずつ書き込み、書き込まれた文字ごとにドットを出力し、50 文字ごとにカウントと改行を出力します。書き込みの問題 (子が何かを読み取っていないため、バッファーがいっぱい) を検出すると、ループを停止し、最終カウントを書き込み、子を強制終了します。

于 2008-12-13T07:31:41.560 に答える