web-dev-qa-db-ja.com

FIFO(名前付きパイプ)は通常のパイプ(名前なしパイプ)とどう違うのですか?

FIFO(名前付きパイプ)は通常のパイプ(|)とどのように異なりますか? Wikipedia から理解できるように、通常のパイプとは異なり、FIFOプロセスが終了した後、パイプは「存続」し、後で削除できます。

しかし、プロセスがパイプ(cat x | grep y)を含むシェルコマンドに基づいている場合、変数またはファイルに格納すると、「プロセスの後でそれを維持する」ことができます。 ?

また、通常のパイプには別のコマンドのstdinとして取得する最初のstdoutもあるので、これも一種の先入れ先出しパイプではありませんか?

13
user9303970

「名前付きパイプ」は、実際にはveryという正確な名前です。名前が(ファイルシステム)。

パイプ— _some-command | grep pattern_で使用される通常の名前のない(「匿名」)パイプは、特別な種類のファイルです。そして、私はファイルを意味します。他のすべてのファイルを実行するのと同じように、ファイルを読み書きします。 Grepは、端末や通常のファイルではなく、パイプから読み取ることを実際には気にしません。

技術的には、舞台裏で起こっていることは、stdin、stdout、およびstderrが、すべてのコマンド実行に渡される3つのオープンファイル(ファイル記述子)であるということです。ファイル記述子(読み取り/書き込み/ etc。ファイルへのすべてのsyscallで使用される)は単なる数値です。 stdin、stdout、およびstderrはファイル記述子0、1、および2です。したがって、シェルが_some-command | grep_を設定すると、次のようになります。

  1. カーネルに匿名パイプを要求します。名前がないため、これは通常のファイルのようにopenでは実行できません。代わりに、2つのファイル記述子を返すpipeまたは_pipe2_で実行されます。⁴

  2. 子プロセスをフォークして(fork()は親プロセスのコピーを作成します。ここではパイプの両側が開いています)、パイプの書き込み側をfd 1(stdout)にコピーします。カーネルには、ファイル記述子番号をコピーするためのsyscallがあります。 dup2()またはdup3()です。次に、読み取り側と書き込み側の他のコピーを閉じます。最後に、execveを使用して_some-command_を実行します。パイプはfd 1なので、_some-command_のstdoutがパイプです。

  3. 別の子プロセスのフォーク。今回は、パイプの読み取り側をfd 0(stdin)に複製し、grepを実行します。そのため、grepはパイプからstdinとして読み取ります。

  4. 次に、それらの子の両方が終了するのを待ちます。

  5. この時点で、カーネルはパイプが開いていないことに気づき、ガーベッジがそれを収集します。それが実際にパイプを破壊するものです。

名前付きパイプは、ファイルシステムに配置することで、匿名パイプに名前を付けます。そのため、anyプロセスは、将来の任意の時点で、通常のopen syscallを使用してパイプのファイル記述子を取得できます。概念的には、すべてのリーダー/ライターがパイプを閉じ、ファイルシステムからunlinkedされるまで、パイプは破棄されません。²

ちなみに、これは一般にUnixでファイルが機能する方法です。 unlinkrmの背後にあるsyscall)は、ファイルの名前の1つを削除するだけです。すべての名前が削除され、ファイルが開かれていない場合にのみ、実際に削除されます。ここでいくつかの答えがこれを探ります:

脚注

  1. 技術的にはこれはおそらく本当ではありません—知ることによっていくつかの最適化を行うことはおそらく可能であり、実際のgrep実装は頻繁に大幅に最適化されています。しかし、概念的には気にしません(実際、grepの簡単な実装は気にしません)。
  2. もちろん、カーネルは実際にすべてのデータ構造をメモリ内に永久に保持するわけではありませんが、最初のプログラムが名前付きパイプを開くたびに、それらを透過的に再作成します(そして、開いている限りそれらを保持します)。つまり、名前が存在する限り、それらは存在していたかのようです。
  3. ターミナルはgrepが読み取る一般的な場所ではありませんが、ターミナルを指定しない場合のデフォルトの標準入力です。したがって、シェルに_grep pattern_と入力すると、grepがターミナルから読み取られます。これが頭に浮かぶのは、ターミナルに何かを貼り付ける場合だけです。
  4. Linuxでは、匿名パイプは実際には特別なファイルシステムpipefsに作成されます。詳細は Linuxでのパイプの動作 を参照してください。これはLinuxの内部実装の詳細であることに注意してください。
20
derobert

パイプラインのシェル構文と基礎となるUnixシステムプログラミングの間で混乱していると思います。パイプ/ FIFOは、ディスクに保存されないタイプのファイルですが、代わりにカーネルのバッファーを介してライターからリーダーにデータを渡します。

パイプ/ FIFOは、ライターとリーダーがopen("/path/to/named_pipe", O_WRONLY);のようなシステムコールを実行するか、または pipe(2) を使用して接続され、新しい匿名パイプを作成するかどうかに関係なく同じように機能します。開いているファイル記述子を読み取り側と書き込み側の両方に返します。

fstat(2) パイプファイル記述子で、いずれかの方法でsb.st_mode & S_IFMT == S_IFIFOを提供します。


foo | barを実行すると:

  • シェルは、非組み込みコマンドに対して通常のようにフォークします
  • 次に、pipe(2)システムコールを実行して、匿名パイプの入力と出力の2つのファイル記述子を取得します。
  • その後、再びforkします。
  • (ここでfork()は0を返しました)
    • パイプの読み取り側を閉じます(書き込みfdは開いたままにします)
    • stdoutdup2(pipefd[1], 1)で書き込みfdにリダイレクトします
    • 次にexecve("/usr/bin/foo", ...)を実行します
  • (ここでfork()は0以外の子PIDを返しました)
    • パイプの書き込み側を閉じます(読み取りfdを開いたままにします)
    • そしてstdindup2(pipefd[0], 0)で読み取りfdからリダイレクトします
    • 次にexecve("/usr/bin/bar", ...)を実行します

foo > named_pipe & bar < named_pipeを実行すると、非常によく似た状況になります。

名前付きパイプは、プロセス間でパイプを確立するためのランデブーです。


状況は、匿名のtmpファイルと名前の付いたファイルに似ています。 O_TMPFILE"/path/to/dir/tmpfile"を開いてリンクを解除し、リンクを解除した場合と同じように、open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);を使用して名前のない一時ファイルを作成できますO_CREAT)。削除されたファイルへのファイル記述子。

O_TMPFILEで作成された場合、linkatを使用して、その匿名ファイルをファイルシステムにリンクし、名前を付けることもできます。 ( ただし、名前を付けて作成したファイルをLinuxで削除してから削除することはできません。

3
Peter Cordes