请问用C这多个管道code有道理? [英] Does this multiple pipes code in C makes sense?

查看:322
本文介绍了请问用C这多个管道code有道理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个<一个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 gets stdin=stdin, stdout=pipe1[1]
    • bar gets stdin=pipe1[0], stdout=pipe2[1]
    • baz gets stdin=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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆