Dashogun と Charlie Martin が指摘したように、これは大きな問題です。彼らの回答には不正確な部分があるので、私も回答します。
独立したスレッドとして実行され、パイプと一緒にフックできる個別のプログラム モジュールを作成することに興味があります。
単一プロセスのスレッド間の通信メカニズムとしてパイプを使用しようとすることには注意してください。1 つのプロセスでパイプの読み取り側と書き込み側の両方が開かれるため、EOF (ゼロ バイト) の表示は決して得られません。
本当にプロセスについて言及しているのであれば、これはツールを構築するための古典的な Unix アプローチの基礎です。標準の Unix プログラムの多くは、標準入力から読み取り、何らかの方法で変換し、結果を標準出力に書き込むフィルターです。たとえば、tr
、sort
、grep
、およびcat
はすべてフィルターです。これは、操作しているデータが許す場合に従うべき優れたパラダイムです。すべてのデータ操作がこのアプローチを助長するわけではありませんが、多くのデータ操作が助長されます。
動機は、各モジュールを完全に独立して記述およびテストできること、おそらくそれらを異なる言語で記述したり、異なるマシンで異なるモジュールを実行したりできることです。
良い点。rsh
or (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 文字ごとにカウントと改行を出力します。書き込みの問題 (子が何かを読み取っていないため、バッファーがいっぱい) を検出すると、ループを停止し、最終カウントを書き込み、子を強制終了します。