web-dev-qa-db-ja.com

名前付きパイプ:いくつかの実験は混乱を招きます

私はさまざまな記事やSO=の質問に出くわしましたが、私が日常的に使用しているものについてはまだ混乱していますが、それがどれほど混乱するか理解していません。パイプ(名前付き)を実験しています。 Linuxで。

1st試してみるのは簡単でした:パイプバッファーがどのように機能しているかを理解します

#1
mkfifo /tmp/mypipe
#2
echo "Hello World" >/tmp/mypipe
ctrl+c
#3
cat /tmp/mypipe

Observationechoがデータを読み取る前にcatを強制終了すると、パイプには何も書き込まれませんでした(catは実行を続けますが、パイプから何も読み取られませんでした)。 producent >named_pipeと入力してproducentを終了すると、パイプバッファーのサイズに一致するデータの一部がnamed_pipeに書き込まれ、それが読み取られるまでここに残ると想定していましたconsument(これがどのように機能するかではないことがわかりました)。それで私が次にしたことは:

2ndconsumentをパイプの他端に接続しようとしました:

#1
mkfifo /tmp/mypipe
#2
echo "Hello World" >/tmp/mypipe
#3
cat /tmp/mypipe

観測catコマンドは"Hello World"メッセージを表示し、両方のプロセスが終了します。ここでの興味深い発見は、#2ステップの間、ps -elfechoコマンドを表示しないことでした。 echoは、誰かがパイプから読み取るまで待機しているようです。これが、最初の試行でパイプに何も出力されなかった理由の説明です。

rd「永久に」実行され、常にパイプに書き込み、何が起こるかを確認するコマンドをパイプすることを試みました。

#1
mkfifo /tmp/mypipe
#2
yes >/tmp/mypipe
#3
cat /tmp/mypipe

観測:これは期待どおりに機能し、catyesがパイプに転送したものを出力しました。ただし、cattail -fに置き換えようとしました。私がこれを行ったとき、tailyesコマンドが強制終了されるまで何も出力しませんでした。

4番目試してみることは大きな謎です:

# 1#
mkfifo /tmp/mypipe

# 2#
for i in $(seq 1 10000); do echo -n $i"|"> /tmp/mypipe; done

# 3#
for i in $(seq 1 10); do echo "${i}# Read:"; cat /tmp/mypipe && echo ""; done

この後、3#コマンドは次のような入力を開始します。

1# Read:
1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|
2# Read:
109|
3# Read:
110|
4# Read:
111|
5# Read:
112|
6# Read:
113|114|115|
7# Read:
116|
8# Read:
117|
9# Read:
118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255|256|257|258|259|260|261|262|263|264|265|266|267|268|269|270|271|272|273|274|275|276|277|278|279|280|281|282|283|284|285|286|287|288|289|290|291|292|293|294|295|
10# Read:
296|297|298|299|300|301|302|303|304|305|306|307|308|309|310|311|312|313|314|315|316|317|318|319|320|321|322|323|324|325|326|327|328|329|

質問

1回目と2回目の試行:

  1. 名前付きパイプは、よく知られているように、従来の|パイプと同等ですか。この特定のケースではbashから?
  2. 生産者は常に消費者を待ちますか?はいの場合、パイプバッファの目的は何ですか?この動作は通信のブロックと呼ばれていますか?
  3. Linuxは、コンシューマーがパイプに接続されていること、つまり通信がいつ発生するかをどのように知るのでしょうか。 lsof named_pipeを試しましたが、何も表示されません。この情報はどこに保存されていますか?私もフォローしてみましたが、結果はcatがパイプから読み取れませんでした。

    #1
    mkfifo /tmp/mypipe
    #2
    echo 1 >/tmp/mypipe
    #3
    rm /tmp/mypipe
    #4
    mkfifo /tmp/mypipe
    #5
    cat /tmp/mypipe
    
  4. 入力中:producent >/tmp/mypipe入力と同じcommand |あるコマンドを別のコマンドにパイプしたいが、パイプの後に別のコマンドを入力し忘れた場合(psこの場合も最初に表示しないcommand)?

回目の試行:

  1. この特定の場合のcattail -fの違いは何ですか?

4回目の試行

  1. ここで何が起こっているのですか?読み取りデータのチャンクが正確なサイズではないのはなぜですか?私は出力を次のように期待していました:

    1#読み取り:1 | 2#読み取り:2 | 3#読み取り:3 |

PS:また、コマンドを開始する順序(最初に読み取るものと後で書き込むもの)を変えてみましたが、結果は同じでした。

PPS:これが明確であることを願っていますが、プロデューサー=パイプに書き込むプロセスです。コンシューマー=パイプから読み取るプロセス。

これは、ほとんどCでスクリプトの知識を持っている人に説明できますか?どうもありがとうございました。

返信への返信:Joe Sewell

  1. OKクリア2。

どちらも並行して実行されること、つまり次の2つは同じではないことを理解しています。

find | less

find > /tmp/file && less /tmp/file

さらに観察したところ、次のコマンドを実行すると、HDDが機能していないことがわかりました。これは、lessコマンドに表示するのに十分なデータが含まれるまで停止しているようです。

find | less

shifg+gを押すと(lessのファイルの終わりに移動します)、HDDがすぐに動作を開始し、データの出力が始まります。これは、lessコマンドに表示するのに十分なデータがある場合、それ以上のデータを生成しないようにfindに何らかの形で指示することを意味しますか?これが同期の意味ですか?また、パイプへのデータ書き込み量はそのバッファサイズに対応していますか? findps auxを押した後、lessが状態(S+ to D+-stat列)をshift+gから変更することにも気付きました

S    interruptible sleep (waiting for an event to complete)
D    uninterruptible sleep (usually IO)
+    is in the foreground process group.

┌─[wakatana@~] [63 files, 178Mb]
└──> ps aux | egrep -w 'less|find'
wakatana     6071  0.0  0.0  12736  1088 pts/5    S+   23:15   0:00 find
wakatana     6072  0.0  0.0   7940   928 pts/5    S+   23:15   0:00 less
wakatana     6183  0.0  0.0   7832   892 pts/6    S+   23:20   0:00 egrep --color=auto -w less|find
┌─[wakatana@~] [63 files, 178Mb]
└──> ps aux | egrep -w 'less|find'
wakatana     6071  0.0  0.0  12808  1304 pts/5    D+   23:15   0:00 find
wakatana     6072  0.0  0.0   9556  2508 pts/5    S+   23:15   0:00 less
wakatana     6193  0.0  0.0   7832   892 pts/6    S+   23:21   0:00 egrep --color=auto -w less|find
  • 誰がこの信号を送信しますか?はいの場合、消費者は彼がすでに生産物を持っているパイプに接続されていることをどのように知っていますか(たとえば、rmパイプの私の例)?

  • OKクリア

  • OKクリア

  • 新しいラインは私を混乱させるケースではないと思います。私の以前の観察に基づいています(そしてあなたは「はい、両端はお互いを待っています」と確認しました)。私はこれを期待していました:

  • I. 1番目のループの最初の反復はパイプに書き込みます。誰も読み取っていないため、ここで待機します。

  • II。 2番目のループが発行されると、1回目の反復で1番目のループによって書き込まれたデータが読み取られます。ここにはこれ以上何も書き込まれないため、これ以上読み取ることはできません。

  • III。 2番目のループは、次のデータが1番目のループによって書き込まれるのを待つか、(順序に関係なく)1番目のループは、書き込まれたデータが2番目のループによって読み取られるまで待機します。

このため、1回の書き込みが1回の読み取りに対応することを期待していました。また、ループが実行されていないかどうかを確認していたので、少し元のコマンドを変更して、コンシューマーが読み取らない場合でも何かがSTDOUTに出力されるかどうかを確認しましたが、何も出力されませんでした。

for i in $(seq 1 10000); do
  if [ $(( $i % 5 )) -eq 0 ]; then
    echo $i;
  else
    echo -n $i"|"> /tmp/mypipe;
  fi;
done

「書き込みプロセスでは改行が送信されないので、読者は「十分だ」と言われるまで読むだけです。

  • 誰が彼が十分に持っていることを消費者に言うでしょうか?

「最初のケースでは、おそらくFIFOのバッファがいっぱいになったためです。」

  • (上記で説明したように)通信がブロックされている場合、どのようにバッファーを埋めることができますか?

「したがって、読者にフラッシュされました。」

  • これはどういう意味ですか?英語でごめんなさい。

「通信を非同期にする方法はありますが...」

  • この場合の非同期と同期の違いは何ですか?
5
Wakan Tanka

質問リストに番号で回答するには:

  1. 名前付きパイプ、別名fifosは、基本的に、シェルによって生成された名前なしパイプと同等です。大きな違いは、シェルバージョンでは両端間の同期は直感的ですが、名前付きパイプを使用しているように見えるため、シェルが何をしているかについて少し知識が必要です。

  2. はい、両端はお互いを待ちます。シェルパイプのようなFIFOの目的は、あるプロセスからの出力を別のプロセスの入力に渡すことです。それらは一時ファイルではありません。あなたが混乱しているのはここだと思います。 cat somefile.txt | lessのようなシェルコマンドの場合、bothコマンドはフォークされたプロセスとして同時に実行され、パイプは2つを同期する役割を果たします。これは、メモリが機能する場合、Cで変更できますが、シェルコマンドでは簡単には変更できません。

  3. プロセスは、パイプのもう一方の端が接続されたときにシグナルを受信できますが、多くの場合、上記のように、2つのプロセスの同期を維持することが目的です。ライターは何かを送信し、書き込み操作が完了したときにそれを続行できることを認識しています。

  4. bashtcshは、パイプを「忘却の中に」入れさせません。コマンドは実行されません。

  5. tail -fは、EOFを取得するまで、この場合はstdinでストリーム全体を読み取ってから、何かを表示する必要があります。あなたの実験では、終わりは決して現れませんでした。一方、catは、入力の処理をすぐに開始できます。

  6. 書き込みプロセスは改行を送信しないため、リーダーは「十分」であると通知されるまで単に読み取ります。最初のケースでは、おそらくFIFOのバッファーがいっぱいになり、リーダーにフラッシュされたことが原因です。後続の出力はおそらく似ており、システムのタイミングに基づいて異なる可能性があります。

ここでもう少し混乱を取り上げましょう。シェルは、コマンドが実行される前にリダイレクトを処理します。つまり、プロセスのリストにcatが表示されないのは、bashがFIFOのもう一方の端が接続するのを待ってスタックしているためですbeforecatまたは実行中のライターを実行します。同様に、ライターが接続されるまで、読み取りコマンドは実行されません。

ここでの最大の誤解は、名前付きパイプが一時ファイルではないことです。名前のないパイプもありません。通信を非同期にする方法はいくつかありますが、doしない限り、/tmpの下に実際の一時ファイルを作成する方がよいようです。両方のプロセスを同時に実行したい。

5
Joe Sewell