You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言实现多进程管道时遇阻塞及execvp执行失败问题求助

多管道Shell实现问题排查

问题描述

用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]);
    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);
}

问题原因分析

1. 管道数组索引严重错误

主函数中定义的管道数组为int fd[(n-1)][2],是包含n-1个管道的二维数组,有效下标为0到n-2。但代码多处错误使用i*2作为索引:

  • 创建管道时pipe(fd[i*2]),当i>=1时会超出数组范围(比如n=3时,i=1访问fd[2]属于越界),导致管道创建到错误内存区域,触发未定义行为。
  • need_to_pipe函数中,首尾命令和中间命令的管道索引均采用i*2/(i-1)*2,同样越界,子进程拿到错误的文件描述符,无法正确重定向标准IO,最终引发execvp失败或进程阻塞。

2. write_pipe函数的错误判断

write_pipe中对dup2返回值的判断逻辑错误:

if ((dup2(fd[WRITE], STDOUT_FILENO)) < -1) 

dup2失败时返回-1,此处判断< -1永远不会成立,导致dup2失败后无法触发错误处理,子进程标准输出重定向失败,后续execvp执行的命令无法正常输出,甚至因文件描述符异常执行失败。

3. 管道文件描述符关闭不规范

  • 中间命令分支中,因索引错误,实际关闭的是无效的管道描述符,上游管道的写端未被正确关闭,下游进程会一直等待数据,导致程序卡住。
  • 主进程关闭管道时同样因索引错误,关闭的是无效描述符,残留的未关闭管道会导致子进程阻塞。

修复建议

  • 修正管道数组索引:所有访问管道数组的地方直接使用i作为下标,比如创建管道改为pipe(fd[i]),need_to_pipe中首尾命令改为write_pipe(fd[i])、read_pipe(fd[i-1]),中间命令改用fd[i-1]和fd[i]。
  • 修正write_pipe的判断条件:改为if (dup2(fd[WRITE], STDOUT_FILENO) < 0)。
  • 确保子进程中所有未使用的管道描述符被正确关闭,避免不必要的阻塞。

内容的提问来源于stack exchange,提问作者user21200173

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 13:25:51