请问用C这多个管道code有道理? [英] Does this multiple pipes code in C makes sense?
问题描述
我创建了一个<一个href=\"http://stackoverflow.com/questions/916900/having-trouble-with-fork-pipe-dup2-and-exec-in-c/\">question这个几天。我的解决办法是在东西是什么在接受的答案提出的方针。然而,我的一个朋友想出了以下解决方案:
I've created a question about this a few days. My solution is something in the lines of what was suggested in the accepted answer. However, a friend of mine came up with the following solution:
请注意,code已经被更新了几次(检查编辑修订),以反映在下面的答案的建议。如果您打算给一个新的答案,请记住这个新的code,而不是旧其中有很多问题这样做。
Please note that the code has been updated a few times (check the edit revisions) to reflect the suggestions in the answers below. If you intend to give a new answer, please do so with this new code in mind and not the old one which had lots of problems.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd[2], i, aux, std0, std1;
do {
std0 = dup(0); // backup stdin
std1 = dup(1); // backup stdout
// let's pretend I'm reading commands here in a shell prompt
READ_COMMAND_FROM_PROMPT();
for(i=1; i<argc; i++) {
// do we have a previous command?
if(i > 1) {
dup2(aux, 0);
close(aux);
}
// do we have a next command?
if(i < argc-1) {
pipe(fd);
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]);
}
// last command? restore stdout...
if(i == argc-1) {
dup2(std1, 1);
close(std1);
}
if(!fork()) {
// if not last command, close all pipe ends
// (the child doesn't use them)
if(i < argc-1) {
close(std0);
close(std1);
close(fd[0]);
}
execlp(argv[i], argv[i], NULL);
exit(0);
}
}
// restore stdin to be able to keep using the shell
dup2(std0, 0);
close(std0);
}
return 0;
}
这是模拟管道一系列的命令像在bash,比如:CMD1 | CMD2 | ... | cmd_n。我说的模拟,因为,正如你所看到的,命令实际上是从参数读取。只是在闲暇时编码一个简单的shell提示...
This simulates a series of commands through pipes like in bash, for instance: cmd1 | cmd2 | ... | cmd_n. I say "simulate", because, as you can see, the commands are actually read from the arguments. Just to spare time coding a simple shell prompt...
当然也有一些问题需要解决,并添加类似的错误处理,但在这里,不是问题的关键。我觉得我还挺得到code,但它仍然使我很多混淆这整件事是如何工作的。
Of course there are some issues to fix and to add like error handling but that's not the point here. I think I kinda get the code but it still makes me a lot of confusing how this whole thing works.
我缺少的东西或本真的作品,这是一个非常干净的解决方案来解决这个问题?如果没有,任何人都可以点我这个code的?
Am I missing something or this really works and it's a nice and clean solution to solve the problem? If not, can anyone point me the crucial problems this code has?
推荐答案
看起来合理,但它确实需要修复漏水 STD
和辅助
给孩子和之后的循环,和家长的原标准输入
是永远失去了。
Looks reasonable, though it really needs to fix leaking std
and aux
to the children and after the loop, and the parent's original stdin
is lost forever.
这很可能是颜色比较好...
This would probably be better with color...
./a.out foo bar baz <stdin >stdout
std = dup(stdout) || |+==========================std
|| || ||
pipe(fd) || || pipe1[0] -- pipe0[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(foo) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe2[0] -- pipe2[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| || || ||
fork+exec(bar) || || || ||
XX || || ||
/-----++-------+| ||
dup2(aux, 0) // || || ||
|| || || ||
close(aux) || || XX ||
|| || ||
pipe(fd) || || pipe3[0] -- pipe3[1] ||
|| || || || ||
aux = fd[0] || || aux || ||
|| XX || || ||
|| /-------++----------+| ||
dup2(fd[1], 1) || // || || ||
|| || || || ||
close(fd[1]) || || || XX ||
|| XX || ||
|| /-------++-----------------+|
dup2(std, 1) || // || ||
|| || || ||
fork+exec(baz) || || || ||
-
富
获得标准输入=标准输入
,标准输出= PIPE1 [1]
-
栏
获得标准输入= PIPE1 [0]
,标准输出= pipe2 [1]
-
巴兹
获得标准输入= pipe2 [0]
,标准输出=标准输出
foo
getsstdin=stdin
,stdout=pipe1[1]
bar
getsstdin=pipe1[0]
,stdout=pipe2[1]
baz
getsstdin=pipe2[0]
,stdout=stdout
我的建议是不同的,因为它避免了重整父母的标准输入
和标准输出
,只有孩子在操纵他们,决不泄露任何函数依赖。这是一个有点难以图,虽然。
My suggestion is different in that it avoids mangling the parent's stdin
and stdout
, only manipulating them within the child, and never leaks any FDs. It's a bit harder to diagram, though.
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
parent
cmds = [foo, bar, baz]
fds = {0: stdin, 1: stdout}
cmd = cmds[0] {
there is a next cmd {
pipe(new_fds)
new_fds = {3, 4}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]}
}
fork => child
there is a next cmd {
close(new_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1]}
dup2(new_fds[1], 1)
fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]}
close(new_fds[1])
fds = {0: stdin, 1: pipe1[1]}
}
exec(cmd)
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[1] {
there is a next cmd {
pipe(new_fds)
new_fds = {5, 6}
fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
}
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe1[0], 1: stdout,
3: pipe1[0], 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe1[0], 1: stdout,
4: pipe1[1],
5: pipe2[0] 6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe1[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
close(new_fds[0])
fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]}
dup2(new_fds[1], 1)
fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]}
close(new_fds[1])
fds = {0: pipe1[0], 1: pipe1[1]}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 4: pipe1[1],
5: pipe2[0], 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]}
}
there is a next cmd {
old_fds = new_fds
old_fds = {3, 4}
}
}
cmd = cmds[2] {
fork => child
there is a previous cmd {
dup2(old_fds[0], 0)
fds = {0: pipe2[0], 1: stdout,
5: pipe2[0], 6: pipe2[1]}
close(old_fds[0])
fds = {0: pipe2[0], 1: stdout,
6: pipe2[1]}
close(old_fds[1])
fds = {0: pipe2[0], 1: stdout}
}
exec(cmd)
there is a previous cmd {
close(old_fds[0])
fds = {0: stdin, 1: stdout, 6: pipe2[1]}
close(old_fds[1])
fds = {0: stdin, 1: stdout}
}
}
您更新code做修复previous FD泄漏&hellip;但增加了一个:你现在漏水 STD0
给孩子们。正如乔恩说,这可能是不危险的大多数程序......但你还是应该写一个表现更好的外壳莫过于此。
Your updated code does fix the previous FD leaks… but adds one: you're now leaking std0
to the children. As Jon says, this is probably not dangerous to most programs... but you still should write a better behaved shell than this.
即使它是暂时的,我会强烈建议反对重整/缩小/ ERR(0/1/2)自己的shell的标准,只有这样做Exec之前右子内。为什么?假设你添加一些的printf
调试在中间,或者你需要摆脱困境,由于错误条件。你会遇到麻烦,如果你不清理混乱的标准文件描述符第一。请,对于具有起见事情的运行,因为即使在意想不到的情况下有望的,不跟他们淤泥,直到您需要。
Even if it's temporary, I would strongly recommend against mangling your own shell's standard in/out/err (0/1/2), only doing so within the child right before exec. Why? Suppose you add some printf
debugging in the middle, or you need to bail out due to an error condition. You'll be in trouble if you don't clean up your messed-up standard file descriptors first. Please, for the sake of having things operate as expected even in unexpected scenarios, don't muck with them until you need to.
正如我在其他意见中提到,分裂成更小的部分使得它更容易理解。这个小助手应该是容易理解的和无缺陷的:
As I mentioned in other comments, splitting it up into smaller parts makes it much easier to understand. This small helper should be easily understandable and bug-free:
/* 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];
}
}
您可以看到猛砸的 execute_cmd.c#execute_disk_command
从 execute_cmd.c#execute_pipeline
被称为,的 XSH 的 process.c#process_run
从 jobs.c#job_run
甚至每一个人 BusyBox的的的各种的小 和的最小的弹分裂他们。
You can see Bash's execute_cmd.c#execute_disk_command
being called from execute_cmd.c#execute_pipeline
, xsh's process.c#process_run
being called from jobs.c#job_run
, and even every single one of BusyBox's various small and minimal shells splits them up.
这篇关于请问用C这多个管道code有道理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!