看起来很合理,尽管它确实需要在循环之后修复泄漏std
和aux
子级,并且父级的原始stdin
文件永远丢失了。
这可能会更好的颜色...
./a.out foo bar baz <stdin >stdout
标准 = dup(标准输出) || |+===========================std
|| || ||
管道(fd) || || 管道1[0] -- 管道0[1] ||
|| || || || ||
辅助 = fd[0] || || 辅助 || ||
|| XX || || ||
|| /--------++---------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
关闭(fd[1]) || || || XX ||
|| || || ||
叉+执行(foo)|| || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
关闭(辅助)|| || XX ||
|| || ||
管道(fd) || || 管道2[0] -- 管道2[1] ||
|| || || || ||
辅助 = fd[0] || || 辅助 || ||
|| XX || || ||
|| /--------++---------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
关闭(fd[1]) || || || XX ||
|| || || ||
叉+执行(酒吧)|| || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
关闭(辅助)|| || XX ||
|| || ||
管道(fd) || || 管道3[0] -- 管道3[1] ||
|| || || || ||
辅助 = fd[0] || || 辅助 || ||
|| XX || || ||
|| /--------++---------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
关闭(fd[1]) || || || XX ||
|| XX || ||
|| /--------++-----------------+|
dup2(std, 1) || // || ||
|| || || ||
fork+exec(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 = [foo, bar, baz]
fds = {0:标准输入,1:标准输出}
cmd = cmds[0] {
有一个下一个 cmd {
管道(new_fds)
new_fds = {3, 4}
fds = {0:标准输入,1:标准输出,3:管道1 [0],4:管道1 [1]}
}
叉子 => 孩子
有一个下一个 cmd {
关闭(new_fds[0])
fds = {0:标准输入,1:标准输出,4:管道1 [1]}
dup2(new_fds[1], 1)
fds = {0:标准输入,1:管道1 [1],4:管道1 [1]}
关闭(new_fds[1])
fds = {0:标准输入,1:管道1 [1]}
}
执行(命令)
有一个下一个 cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[1] {
有一个下一个 cmd {
管道(new_fds)
new_fds = {5, 6}
fds = {0:标准输入,1:标准输出,3:管道1 [0],4:管道1 [1],
5:管道2[0],6:管道2[1]}
}
叉子 => 孩子
有一个以前的 cmd {
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]}
}
有一个下一个 cmd {
关闭(new_fds[0])
fds = {0: pipe1[0], 1: 标准输出, 6: pipe2[1]}
dup2(new_fds[1], 1)
fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]}
关闭(new_fds[1])
fds = {0: pipe1[0], 1: pipe1[1]}
}
执行(命令)
有一个以前的 cmd {
关闭(old_fds[0])
fds = {0:标准输入,1:标准输出,4:管道1 [1],
5:管道2[0],6:管道2[1]}
关闭(old_fds[1])
fds = {0:标准输入,1:标准输出,5:管道2 [0],6:管道2 [1]}
}
有一个下一个 cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[2] {
叉子 => 孩子
有一个以前的 cmd {
dup2(old_fds[0], 0)
fds = {0:管道2 [0],1:标准输出,
5:管道2[0],6:管道2[1]}
关闭(old_fds[0])
fds = {0:管道2 [0],1:标准输出,
6:管道2[1]}
关闭(old_fds[1])
fds = {0: pipe2[0], 1: 标准输出}
}
执行(命令)
有一个以前的 cmd {
关闭(old_fds[0])
fds = {0:标准输入,1:标准输出,6:管道2 [1]}
关闭(old_fds[1])
fds = {0:标准输入,1:标准输出}
}
}
编辑
您更新的代码确实修复了以前的 FD 泄漏……但添加了一个:您现在正在泄漏std0
给孩子们。正如 Jon 所说,这对大多数程序来说可能并不危险......但你仍然应该编写一个比这更好的 shell。
即使它是暂时的,我强烈建议不要修改你自己的 shell 的标准输入/输出/错误(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
调用 from execute_cmd.c#execute_pipeline
,xshprocess.c#process_run
被调用 from ,甚至BusyBox的各种小型和最小外壳中的jobs.c#job_run
每一个都将它们分开。