合理的に見えますが、実際にはリークを修正する必要がstd
ありaux
、子とループの後に、親のオリジナルstdin
は永遠に失われます。
これは色をつけたほうがいいかも…
./a.out foo bar baz <stdin >stdout
std = dup(stdout) || |+==========================標準
|| || ||
パイプ (fd) || || パイプ1[0] -- パイプ0[1] ||
|| || || || ||
補助 = fd[0] || || || 補助 || ||
|| XX || || ||
|| /---------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
閉じる (fd[1]) || || || XX ||
|| || || ||
フォーク + 実行 (foo) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
閉じる (aux) || || XX ||
|| || ||
パイプ (fd) || || パイプ2[0] -- パイプ2[1] ||
|| || || || ||
補助 = fd[0] || || || 補助 || ||
|| XX || || ||
|| /---------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
閉じる (fd[1]) || || || XX ||
|| || || ||
fork+exec(バー) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
閉じる (aux) || || XX ||
|| || ||
パイプ (fd) || || パイプ3[0] -- パイプ3[1] ||
|| || || || ||
補助 = fd[0] || || || 補助 || ||
|| XX || || ||
|| /---------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
閉じる (fd[1]) || || || XX ||
|| XX || ||
|| /------++-----------------+|
dup2(標準、1) || // || ||
|| || || ||
フォーク + 実行 (baz) || || || ||
foo
、stdin=stdin
_stdout=pipe1[1]
bar
、stdin=pipe1[0]
_stdout=pipe2[1]
baz
、stdin=pipe2[0]
_stdout=stdout
stdin
私の提案は、親のandのマングリングを回避stdout
し、子内でのみ操作し、FD をリークしないという点で異なります。ただし、図にするのは少し難しいです。
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
親
cmds = [フー、バー、バズ]
fds = {0: 標準入力、1: 標準出力}
cmd = cmds[0] {
次のコマンドがあります {
パイプ(new_fds)
new_fds = {3, 4}
fds = {0: 標準入力、1: 標準出力、3: パイプ1[0]、4: パイプ1[1]}
}
フォーク => 子
次のコマンドがあります {
閉じる (new_fds[0])
fds = {0: stdin、1: stdout、4: pipe1[1]}
dup2(new_fds[1], 1)
fds = {0: 標準入力、1: パイプ 1[1]、4: パイプ 1[1]}
閉じる (new_fds[1])
fds = {0: stdin、1: pipe1[1]}
}
実行(コマンド)
次のコマンドがあります {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmd[1] {
次のコマンドがあります {
パイプ(new_fds)
new_fds = {5, 6}
fds = {0: stdin、1: stdout、3: pipe1[0]、4: pipe1[1]、
5: パイプ2[0]、6: パイプ2[1]}
}
フォーク => 子
前のコマンドがあります {
dup2(old_fds[0], 0)
fds = {0: パイプ 1[0]、1: 標準出力、
3: パイプ1[0]、4: パイプ1[1]、
5: パイプ2[0]、6: パイプ2[1]}
閉じる (old_fds[0])
fds = {0: パイプ 1[0]、1: 標準出力、
4: パイプ1[1]、
5: パイプ2[0] 6: パイプ2[1]}
閉じる (old_fds[1])
fds = {0: パイプ 1[0]、1: 標準出力、
5: パイプ2[0]、6: パイプ2[1]}
}
次のコマンドがあります {
閉じる (new_fds[0])
fds = {0: パイプ 1[0]、1: 標準出力、6: パイプ 2[1]}
dup2(new_fds[1], 1)
fds = {0: パイプ 1[0]、1: パイプ 2[1]、6: パイプ 2[1]}
閉じる (new_fds[1])
fds = {0: パイプ 1[0]、1: パイプ 1[1]}
}
実行(コマンド)
前のコマンドがあります {
閉じる (old_fds[0])
fds = {0: stdin、1: stdout、4: pipe1[1]、
5: パイプ2[0]、6: パイプ2[1]}
閉じる (old_fds[1])
fds = {0: 標準入力、1: 標準出力、5: パイプ 2[0]、6: パイプ 2[1]}
}
次のコマンドがあります {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[2] {
フォーク => 子
前のコマンドがあります {
dup2(old_fds[0], 0)
fds = {0: pipe2[0]、1: stdout、
5: パイプ2[0]、6: パイプ2[1]}
閉じる (old_fds[0])
fds = {0: pipe2[0]、1: stdout、
6: パイプ2[1]}
閉じる (old_fds[1])
fds = {0: pipe2[0]、1: stdout}
}
実行(コマンド)
前のコマンドがあります {
閉じる (old_fds[0])
fds = {0: stdin、1: stdout、6: pipe2[1]}
閉じる (old_fds[1])
fds = {0: 標準入力、1: 標準出力}
}
}
編集
更新されたコードは、以前の FD リークを修正しますが、1 つ追加します。現在std0
、子にリークしています。Jon が言うように、これはおそらくほとんどのプログラムにとって危険ではありませんが、それでもこれよりも動作の良いシェルを作成する必要があります。
一時的であっても、自分のシェルの標準の in/out/err (0/1/2) をマングリングしないことを強くお勧めします。これは、exec の直前の子内でのみ行います。なんで?途中でデバッグを追加しprintf
たり、エラー状態のために救済する必要があるとします。混乱した標準ファイル記述子を最初にクリーンアップしないと、問題が発生します。予期しないシナリオでも期待どおりに動作させるために、必要になるまでいじらないでください。
編集
他のコメントで述べたように、それをより小さな部分に分割すると、理解しやすくなります。この小さなヘルパーは、簡単に理解でき、バグがないはずです。
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
これがそうであるように:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
Bashがexecute_cmd.c#execute_disk_command
から呼び出されexecute_cmd.c#execute_pipeline
、xshがからprocess.c#process_run
呼び出され、 BusyBoxのさまざまな小さくて最小限のシェルjobs.c#job_run
のすべてがそれらを分割していることがわかります。