Bashでプロセス交換を実装する方法は?

Bashでプロセス交換を実装する方法は?

研究中です。その他の問題、後ろで何が起こっているのか、/dev/fd/*これらのファイルが何であるか、サブプロセスがファイルをどのように開いたのか理解していないことに気づきました。

答え1

まあ、そこには多くの側面があります。

ファイル記述子

各プロセスに対して、カーネルは開かれたファイルテーブルを維持します(もちろん、別の方法で実装されるかもしれませんが、とにかく見ることができないので、単純なテーブルであると仮定できます)。このテーブルには、ファイルが存在する場所や場所が見つかる、ファイルが開いているモード、現在読み書きする場所、実際にファイルの入出力操作を実行するために必要なその他の情報が含まれています。これで、プロセスはテーブルを読み書きできません。プロセスがファイルを開くと、ファイル記述子であることを返します。これはテーブルへのインデックスにすぎません。

ディレクトリ/dev/fdと内容

Linuxではdev/fd実際に/proc/self/fd/procカーネルがファイルAPIを使用してアクセスする複数の内部データ構造をマップする擬似ファイルシステムです(プログラムの通常のファイル/ディレクトリ/シンボリックリンクのように見えます)。特にすべてのプロセスに関する情報です(ここで名前が由来しました)。シンボリックリンクは、/proc/self常に現在実行中のプロセス(つまり、それを要求したプロセス、したがってプロセスごとに異なる値が表示される)に関連するディレクトリを参照します。プロセスのディレクトリには、fd開いている各ファイルに、そのファイルを対象とするファイル記述子(プロセスファイルテーブルのインデックス、前のセクションを参照)の10進表現で名前が付けられたシンボリックリンクを含むサブディレクトリがあります。

サブプロセス生成時のファイル記述子

fork子プロセスは.Aによってファイル記述子のコピーが作成されますfork。これは、子プロセスが親プロセスとまったく同じオープンファイルのリストで作成されることを意味します。したがって、子プロセスが開いているファイルの1つを閉じない限り、子プロセスから継承されたファイル記述子にアクセスすると、親プロセスの元のファイル記述子にアクセスするのと同じファイルにアクセスします。

分岐後、最初は同じプロセスの2つのコピーがあり、分岐呼び出しの戻り値のみが異なります(親は子のPIDを取得し、子は0を取得します)。通常、exec実行可能ファイルの1つのコピーを別のコピーに置き換えるためにフォークが実行されます。開いたファイル記述子は、実行後もまだ存在します。実行前に、プロセスは他のタスク(たとえば、新しいプロセスがインポートしないでくださいファイルを閉じたり、他のファイルを開くなど)を実行したりできます。

無名パイプ

名前のないパイプはカーネルの要求によって生成されたファイル記述子のペアであるため、最初のファイル記述子に書き込まれたすべての内容は2番目のファイル記述子に渡されます。最も一般的な用途は、foo | barパイプ構成では、bash標準出力がfooパイプの書き込み部分に置き換えられ、標準入力が読み取り部分に置き換えられることです。 stdinとstdoutはファイルテーブルの最初の2つのエントリ(項目0と1、2は標準エラー)です。することができます)。プロセスがテーブルに直接アクセスできないため、これを行うカーネル関数があります。

プロセスの置き換え

これですべてが準備されているので、プロセス置換がどのように機能するかを確認できます。

  1. Bashプロセスは、後で作成される2つのプロセス間の通信に使用される名前のないパイプを生成します。
  2. Bashはプロセスを分岐しますecho。子プロセス(元のプロセスの正確なコピーbash)はパイプの読み取り端を閉じ、独自の標準出力をパイプの書き込み端に置き換えます。これがechoシェル組み込みであることを考慮すると、それ自体bashで呼び出しを保存できますが、exec問題ではありません(シェル組み込みも無効になる可能性があり、この場合はexecを実行します/bin/echo)。
  3. Bash(元の親)は、<(echo 1)名前のないパイプの読み取りの終わりを参照したときに式を疑似ファイルリンクに置き換えました。/dev/fd
  4. PHPプロセスのBashランチャー(フォーク後もまだbashの[コピー]にあることに注意してください)。新しいプロセスは、名前付きパイプの継承された書き込み側を閉じて別の準備手順を実行しますが、読み取り側は開いたままにします。次にPHPを実行します。
  5. PHPプログラムは、/dev/fd/対応するファイル記述子がまだ開かれているので、パイプの読み出し端に相当します。したがって、PHPプログラムが読み取り用に特定のファイルを開く場合、実際に実行することは、名前のないパイプのsecond読み取り終了のためのファイル記述子を生成することです。しかし、問題ありません。どちらからでも読むことができます。
  6. これで、PHPプログラムは新しいファイル記述子を介してパイプの読み取り端を読み取ることができるため、echo同じパイプの書き込み端からコマンドの標準出力を受け取ることができます。

答え2

celtschk答えから借りたのは/dev/fdシンボリックリンクです/proc/self/fd/procプロセスに関する情報やその他のシステム情報を階層ファイル構造として表示する擬似ファイルシステム。ファイル内のファイルは、/dev/fdプロセスが開いたファイルに対応し、ファイル記述子を名前で持ち、ファイル自体を対象としています。ファイルを開くことは、/dev/fd/N記述子をコピーするのと同じですN(説明者がN開いていると仮定)。

どのように動作するかについて私が見つけた結果は次のとおりです(strace出力に何が起こっているのかをよりよく表現するために不要な詳細が削除され修正されました)。

$ cat 1.c
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    char buf[100];
    int fd;
    fd = open(argv[1], O_RDONLY);
    read(fd, buf, 100);
    write(STDOUT_FILENO, buf, n_read);
    return 0;
}
$ gcc 1.c -o 1.out
$ cat 2.c
#include <unistd.h>
#include <string.h>

int main(void)
{
    char *p = "hello, world\n";
    write(STDOUT_FILENO, p, strlen(p));
    return 0;
}
$ gcc 2.c -o 2.out
$ strace -f -e pipe,fcntl,dup2,close,clone,close,execve,wait4,read,open,write bash -c './1.out <(./2.out)'
[bash] pipe([3, 4]) = 0
[bash] dup2(3, 63) = 63
[bash] close(3) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p2
Process p2 attached
[bash] close(4) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p1
Process p1 attached
[bash] close(63) = 0
[p2] dup2(4, 1) = 1
[p2] close(4) = 0
[p2] close(63) = 0
[bash] wait4(-1, <unfinished ...>
Process bash suspended
[p1] execve("/home/yuri/_/1.out", ["/home/yuri/_/1.out", "/dev/fd/63"], [/* 31 vars */]) = 0
[p2] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p22
Process p22 attached
[p22] execve("/home/yuri/_/2.out", ["/home/yuri/_/2.out"], [/* 31 vars */]) = 0
[p2] wait4(-1, <unfinished ...>
Process p2 suspended
[p1] open("/dev/fd/63", O_RDONLY) = 3
[p1] read(3,  <unfinished ...>
[p22] write(1, "hello, world\n", 13) = 13
[p1] <... read resumed> "hello, world\n", 100) = 13
Process p2 resumed
Process p22 detached
[p1] write(1, "hello, world\n", 13) = 13
hello, world
[p2] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p22
[p2] --- SIGCHLD (Child exited) @ 0 (0) ---
[p2] wait4(-1, 0x7fff190f289c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Process bash resumed
Process p1 detached
[bash] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p1
[bash] --- SIGCHLD (Child exited) @ 0 (0) ---
Process p2 detached
[bash] wait4(-1, 0x7fff190f2bdc, WNOHANG, NULL) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
[bash] wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = p2
[bash] wait4(-1, 0x7fff190f299c, WNOHANG, NULL) = -1 ECHILD (No child processes)

デフォルトではbashパイプを作成し、その末尾をファイル記述子として子に渡します(終わりを読み1.out、終わりを書きます2.out)。1.out()にコマンドライン引数として読み取り出口を渡します/dev/fd/63。このメソッドは1.out開くことができます/dev/fd/63

関連情報