在C语言中多个进程之间的管道传输

3

我正在用 C 语言写一个 shell,并尝试实现多个管道。我通过创建带有管道的二维数组并每次使用单独的管道来完成这个目标。管道之间的所有命令都由一个解析函数分开并放入结构体中。在每个管道之间的每条命令行都有自己的进程。对于中间的所有命令,我尝试从前一个进程读取并写入下一个进程。问题似乎就出在这里。当我尝试使用多个管道时,一个管道可以正常工作,但是当我使用多个管道时,程序没有输出并且被卡住了。在 GDB 中,在第二个进程 fork 后,我会收到 execvp 失败的消息。这可能是什么原因?


int create_pipe(int* fd)
{
  int pipe_id = pipe(fd); 
  if (pipe_id == -1)
  {
    return -1; 

  }
  return 0; 
}

void write_pipe(int* fd)
{
  close(fd[READ]);
  if ((dup2(fd[WRITE], STDOUT_FILENO)) < -1) 
  {
    fork_error(); 
  }
  close(fd[WRITE]);
 
}

void read_pipe(int *fd)
{
  close(fd[WRITE]);
  if (dup2(fd[READ], STDIN_FILENO) < 0)
  {
    fork_error();
  }
  close(fd[READ]);
  
}

void need_to_pipe (int i, int (*fd)[2])
{

  if (commands[i].pos == first)
  { 
    write_pipe(fd[i * 2]);
  }
  else if (commands[i].pos == last)
  {
    read_pipe(fd[(i-1) *2]); 
  }
  else //if (commands[i].pos == middle) 
  {
    
      dup2(fd[(i-1)*2][READ], STDIN_FILENO);
      close(fd[(i-1)*2][READ]);
      close(fd[(i-1)*2][WRITE]);
      //close(fd[(i)*2][READ]);
      //close(fd[(i)*2][WRITE]);
      close(fd[(i)*2][READ]);
      dup2(fd[i*2][WRITE], STDOUT_FILENO);
      close(fd[(i)*2][WRITE]); 
  }
  
  
}

void fork_cmd(int i, int (*fd)[2]) {

  pid_t pid; 
  switch (pid = fork()) {
    case -1:
      fork_error();
    case 0:
      if (!(commands[i].pos == single))
      {
        need_to_pipe(i, fd);
      }
      if (execvp(commands[i].argv[0], commands[i].argv)<0)
      {

        exit(EXIT_FAILURE);
      }
  }
}

void fork_cmds(int n, int (*fd)[2]) 
{
  for (int i = 0; i < n; i++) 
  {
    fork_cmd(i, fd);
  }
}


void wait_once ()
{
  wait(NULL);
}

void wait_for_all_cmds(int n) 
{
  for (int i = 0; i < n; i++)
  {
      wait_once();
     //wait for number of child processes. 
  }

}


int main() {
  int n;               
  size_t size = 256;   
  char line[size];        

  while(true) {
       

    get_line(line, size);

    n = parse_cmds(line, commands);

    int fd[(n-1)][2]; 
    for(int i =0;i<n-1;i++)
    {
      int pipe_id = pipe(fd[i*2]); 

      if (pipe_id == -1)
      {
        return -1; 

      }
    }

    fork_cmds(n, fd);

    for(int i =0;i<n-1;i++)
    {
      int *fdclose= fd[i*2]; 

      close (fdclose[READ]);
      close (fdclose[WRITE]);
    }
    
    wait_for_all_cmds(n);
    
  }

  exit(EXIT_SUCCESS);
}


附注:printf(" >>> "); ---> 要么添加\n,要么刷新stdout。否则您可能看不到提示符。 - Haris
你的函数 int create_pipe(int* fd) { int pipe_id = pipe(fd); if (pipe_id == -1) { return -1; } return 0; } 是一种冗长的写法,可以简化为 int create_pipe(int* fd) { return pipe(fd); } — 然后你其实不需要 create_pipe() 函数了(直接调用 pipe() 即可)。在 create_pipe() 函数体中多出来的5行代码没有任何好处。 - Jonathan Leffler
1个回答

2

你[可能]有太多进程保持了管道端口打开(不属于给定的子级),因为你的循环在任何分叉之前都会打开所有管道。

这会给每个子进程带来不必要的负担,因为它必须关闭许多管道端口,以防止它保持管道端口打开状态,从而阻止其他子进程看到它们输入管道上的EOF。


为了在您现有的代码中进行调试,可以让子进程在exec*调用之前执行以下操作(例如):
pid_t pid = getpid();

fprintf(stderr, "child: %d\n", pid);
fflush(stderr);

char cmd[100];
sprintf(cmd,"ls -l /proc/%d/fd 1>&2",pid);

system(cmd);

每个子进程应该只有三个打开的流,分别是stdinstdoutstderr(例如0、1、2)。
我认为你会发现各个子进程上有许多无关/有害的流处于打开状态。
您只需要两个管道数组(例如):int pipeinp [2];int pipeout [2]; 最初,pipeinp 全部为 -1。
大致的步骤是 ...
父进程应该在fork_cmd顶部(fork之前)执行一次单独的pipe调用到pipeout
子进程复制(并关闭)pipeinp的读端[如果不是-1] 到stdin
子进程复制/关闭pipeout的写端到stdout
它关闭pipeout的读端。
之后,父进程应该将pipeout复制到pipeinp中,并关闭pipeinp的写端
这应该针对所有管道阶段重复进行。

最后一个命令不应该使用pipepipeout。而且,最后的子进程不应该改变stdout


有一个可工作的示例,请参见我的答案:fd泄漏,自定义Shell


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接