質問
私は、パイプで接続できる独立したスレッドとして実行される個別のプログラム モジュールを作成することに興味があります。動機は、各モジュールを完全に独立して作成してテストできること、さらには異なる言語で作成したり、異なるマシンで異なるモジュールを実行したりできることです。ここにはさまざまな可能性があります。しばらくパイピングを使用したことがありますが、その動作の微妙な違いについてはよくわかりません。
- 受信側は入力の待機をブロックするようですが、これは予想どおりですが、送信側は誰かがストリームから読み取るのを待機することもありますか?
- ストリームに eof を書き込んだ場合、そのストリームを閉じるまで書き込みを続けることができますか?
- 名前付きパイプと名前なしパイプの動作に違いはありますか?
- 名前付きパイプを使用してパイプのどちらの端を最初に開くかは重要ですか?
- パイプの動作は異なる Linux システム間で一貫していますか?
- パイプの動作は、使用しているシェルまたはその構成方法に依存しますか?
- この方法でパイプを使用する場合に他に尋ねるべき質問や注意すべき問題はありますか?
解決
うわー、質問がたくさんあります。すべてをカバーできるかどうか見てみましょう...
受信側が入力を待っていることをブロックするようです。
実際の「読み取り」呼び出しは、何かが存在するまでブロックされると正しく予想されます。ただし、パイプ内で何が (そしてどれだけ) 待機しているのかを「覗く」ことができる C 関数がいくつかあると思います。残念ながら、これもブロックされるかどうかは覚えていません。
送信エンドブロックは、誰かがストリームから読むのを待つことがありますか
いいえ、送信をブロックすることはできません。これがネットワークを介して別のコンピューターへのパイプだった場合の影響を考えてみましょう。他のコンピュータが受信したことを応答するまで (おそらく長い遅延が発生する可能性がありますが) 待ちますか?宛先のリーダー ハンドルが閉じられている場合は、別のケースになります。この場合、それを処理するために何らかのエラー チェックを行う必要があります。
ストリームにeofを書き込むと、 そのストリームへの書き込みを続行する 私がそれを閉じるまで
これは、使用している言語とそのパイプの実装に依存すると思います。Cではノーと言うでしょう。Linux シェルでは、「はい」と言えます。より経験豊富な他の誰かがそれに答える必要があります。
動作に違いはありますか 名前付きパイプと名前なしパイプ?私の知る限り、そうです。ただし、私は名前付きと名前なしの経験があまりありません。違いは次のとおりだと思います。
- 単方向通信と双方向通信
- スレッドの「in」ストリームと「out」ストリームへの読み取りと書き込み
名前付きパイプで最初に開くパイプのどの端が重要ですか?
通常は不要ですが、スレッドを作成して相互にリンクしようとする初期化時に問題が発生する可能性があります。すべてのサブスレッドを作成し、それぞれのパイプを相互に同期するメイン スレッドが 1 つ必要になります。
パイプの動作は、異なるLinuxシステム間で一貫していますか?
繰り返しますが、これは言語によって異なりますが、一般的にはそうです。POSIX について聞いたことがありますか?それが標準です (少なくとも Linux では、Windows が独自の処理を行います)。
パイプの挙動は依存しますか 私が使っているシェルや私が使っている方法で 設定しましたか?
これはもう少しグレーゾーンに入りつつあります。答え すべき シェルは本質的にシステムコールを行う必要があるため、no にします。ただし、それまでのことはすべて手当たり次第です。
私が尋ねるべき他の質問はありますか
あなたが尋ねた質問は、あなたがこのシステムについて十分に理解していることを示しています。研究を続けて、どのレベル (シェル、C など) に取り組むかに焦点を当ててください。ただ試してみるだけでも、さらに多くのことを学ぶことができます。
他のヒント
これはすべて UNIX のようなシステムに基づいています。最近のバージョンの Windows の具体的な動作についてはよくわかりません。
受信側は入力の待機をブロックするようですが、これは予想どおりですが、送信側は誰かがストリームから読み取るのを待機することもありますか?
はい、ただし、最新のマシンでは頻繁には起こらないかもしれません。パイプには中間バッファーがあります。 できる 埋まる可能性があります。そうなった場合、パイプの書き込み側は実際にブロックされます。しかし、よく考えてみると、このような危険を冒すほど大きなファイルはそれほど多くありません。
ストリームに eof を書き込んだ場合、そのストリームを閉じるまで書き込みを続けることができますか?
ええと、CTRL-D、0x04 のような意味ですか?もちろん、ストリームがそのように設定されている限りは可能です。つまり。
506 # cat | od -c
abc
^D
efg
0000000 a b c \n 004 \n e f g \n
0000012
名前付きパイプと名前なしパイプの動作に違いはありますか?
はい、しかしそれらは微妙で実装に依存します。最大の利点は、もう一方の端が実行される前に名前付きパイプに書き込むことができることです。名前のないパイプを使用すると、フォーク/実行プロセス中にファイル記述子が共有されるため、プロセスが起動していないと一時バッファにアクセスする方法がありません。
名前付きパイプを使用してパイプのどちらの端を最初に開くかは重要ですか?
いいえ。
パイプの動作は異なる Linux システム間で一貫していますか?
当然のことながら、そうです。バッファサイズなどは異なる場合があります。
パイプの動作は、使用しているシェルまたはその構成方法に依存しますか?
いいえ。パイプを作成すると、内部では親プロセス (シェル) がファイル記述子のペアを持つパイプを作成し、次の疑似コードのような fork exec を実行します。
親:
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
.]
Dashogun と Charlie Martin が指摘したように、これは大きな問題です。彼らの回答には不正確な部分もありましたので、私も回答させていただきます。
私は、パイプで接続できる独立したスレッドとして実行される個別のプログラム モジュールを作成することに興味があります。
単一プロセスのスレッド間の通信メカニズムとしてパイプを使用しようとする場合は注意してください。1 つのプロセスでパイプの読み取り端と書き込み端の両方を開くことになるため、EOF (ゼロ バイト) 表示が表示されることはありません。
本当にプロセスを指しているのであれば、これはツールを構築するための古典的な Unix アプローチの基礎です。標準 Unix プログラムの多くは、標準入力から読み取り、何らかの方法で変換し、結果を標準出力に書き込むフィルターです。例えば、 tr
, sort
, grep
, 、 そして cat
いくつか例を挙げると、これらはすべてフィルタです。これは、操作しているデータが許可する場合に従うべき優れたパラダイムです。すべてのデータ操作がこのアプローチに適しているわけではありませんが、適しているデータ操作は数多くあります。
動機は、各モジュールを完全に独立して作成してテストできること、さらには異なる言語で作成したり、異なるマシンで異なるモジュールを実行したりできることです。
良い点。実際にはマシン間にパイプ メカニズムがないことに注意してください。ただし、次のようなプログラムを使用してパイプ メカニズムに近づけることはできます。 rsh
または(より良い) ssh
. 。ただし、内部的には、このようなプログラムはパイプからローカル データを読み取り、そのデータをリモート マシンに送信する可能性がありますが、マシン間の通信はパイプを使用せず、ソケットを介して行われます。
ここにはさまざまな可能性があります。しばらくパイピングを使用したことがありますが、その動作の微妙な違いについてはよくわかりません。
わかりました;質問することは学習の(良い)方法の 1 つです。もちろん、実験することは別です。
受信側は入力の待機をブロックするようですが、これは予想どおりですが、送信側は誰かがストリームから読み取るのを待機することもありますか?
はい。パイプバッファのサイズには制限があります。従来、これは非常に小さく、4096 または 5120 が一般的な値でした。最近の Linux ではより大きな値が使用されている場合があります。使用できます fpathconf()
および _PC_PIPE_BUF を使用してパイプ バッファーのサイズを確認します。POSIX では、バッファーが 512 であることのみが必要です (つまり、_POSIX_PIPE_BUF は 512)。
ストリームに eof を書き込んだ場合、そのストリームを閉じるまで書き込みを続けることができますか?
技術的には、EOF をストリームに書き込む方法はありません。EOF を示すためにパイプ記述子を閉じます。Control-D または Control-Z を EOF 文字として考えている場合、パイプに関する限り、それらは単なる通常の文字です。正規モード (クックされた) で実行されている端末で入力された場合にのみ、EOF のような効果があります。 、または通常)。
名前付きパイプと名前なしパイプの動作に違いはありますか?
はいといいえ。最大の違いは、名前のないパイプは 1 つのプロセスによって設定される必要があり、そのプロセスと、そのプロセスを共通の祖先として共有する子プロセスによってのみ使用できることです。対照的に、名前付きパイプは、以前は関連付けられていなかったプロセスで使用できます。次の大きな違いは、最初の違いの結果です。名前のないパイプを使用すると、単一の関数 (システム) 呼び出しから 2 つのファイル記述子が返されます。 pipe()
, ただし、通常のメソッドを使用して FIFO または名前付きパイプを開きます。 open()
関数。(誰かがFIFOを作成する必要があります mkfifo()
開く前に電話してください。名前なしパイプでは、そのような事前設定は必要ありません。) ただし、ファイル記述子を開いた後は、名前付きパイプと名前なしパイプの間にほとんど違いはありません。
名前付きパイプを使用してパイプのどちらの端を最初に開くかは重要ですか?
いいえ。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;
}
他のプラットフォームから追加の結果を取得したいと考えています。私が見つけたサイズはこちらです。すべての結果は私が予想していたよりも大きく、正直に告白しなければなりませんが、バッファ サイズに関しては、チャーリーと私は「かなり大きい」の意味について議論しているかもしれません。
- 8196 - HP-UX 11.23 for IA-64 (fcntl(F_SETFL) が失敗しました)
- 16384 - ソラリス 10
- 16384 - MacOS X 10.5 (fcntl(F_SETFL) は失敗しませんでしたが、O_NONBLOCK が機能しませんでした)
- 32768 - AIX 5.3
- 65536 - Cygwin / Windows XP (fcntl(F_SETFL) は失敗しませんでしたが、O_NONBLOCK は機能しませんでした)
- 65536 - SuSE Linux 10 (および CentOS) (fcntl(F_SETFL) が失敗しました)
これらのテストから明らかな点の 1 つは、O_NONBLOCK は一部のプラットフォームではパイプで動作し、他のプラットフォームでは動作しないということです。
プログラムはパイプとフォークを作成します。子はパイプの書き込み側を閉じ、シグナルを受信するまでスリープ状態になります。これが、pause() の動作です。次に、親はパイプの読み取り側を閉じ、書き込み記述子にフラグを設定して、パイプ全体への書き込みがブロックされないようにします。次にループして、一度に 1 文字ずつ書き込み、書かれる文字ごとにドットを出力し、50 文字ごとにカウントと改行を出力します。書き込みの問題 (子が何も読み取っていないためバッファがいっぱい) を検出すると、ループを停止し、最終カウントを書き込み、子を強制終了します。