自研C语言shell的管道操作异常出现无限读取,该如何排查修复?
你的代码存在以下几处核心错误,按优先级排序如下:
1. 管道创建越界
你定义的管道数组是 int fd[no_commands-1][2],总共有 no_commands-1 个管道,但是创建管道的循环写的是 for (i = 0; i < no_commands; i++) pipe(fd[i]);,当i等于no_commands-1时就会越界访问数组,触发未定义行为。
2. 父进程未关闭管道写端,导致读端永远收不到EOF
管道的底层逻辑是:只有所有持有管道写端的进程都关闭了写端,读端的read调用才会返回0(即EOF)。你当前只在父进程里关闭了所有管道的读端,所有写端都没有关闭,哪怕子进程执行完关闭了自己持有的写端,父进程还持有写端,导致读端进程(比如你例子里的grep)永远等不到EOF,就会陷入无限等待。
3. execute_command 函数逻辑完全错误
这是最核心的功能性错误:
- 条件判断逻辑错误:你传的无文件描述符的标记是
-1,但判断条件写的是if (fd_in),-1在C语言里是非0值,会被判定为真,哪怕没有输入重定向也会进入分支 - 文件描述符操作顺序错误:你先
close(fd_in)再调用dup2,相当于把要复制的源文件描述符先关了,dup2会直接报错,完全达不到重定向的效果 - 重定向分支逻辑错误:每个重定向分支里都调用了
execvp,如果同时有输入和输出重定向(即中间管道节点),处理完输入重定向就直接执行命令了,根本不会处理输出重定向 - 多条件判断语法错误:
if (fd_in == fd_out == -1)的写法不符合C语言语法,C语言会先判断fd_in == fd_out得到布尔值0或1,再和-1比较,这个判断永远为假 - 子进程未处理exec失败的情况:如果
execvp调用失败,子进程会继续执行父进程的逻辑,导致进程逻辑混乱
4. 进程等待逻辑错误
你在execute_command里fork之后立刻调用waitpid等待当前子进程执行完,才会fork下一个子进程。管道是需要上下游进程并行运行的,如果上游命令输出的数据超过管道缓冲区大小,上游会被阻塞等待下游读取,而你还没fork下游进程,就会直接死锁。
修复方案
第一步:修复execute_commands函数
#include <sys/wait.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> void execute_commands(struct command mycommands[], int no_commands) { if (no_commands == 1) { execute_command(mycommands[0].comm, mycommands[0].no_args, mycommands[0].args, -1, -1); // 等待单个命令执行完成 wait(NULL); return; } int i = 0; int fd[no_commands-1][2]; // 修复:只创建no_commands-1个管道 for (i = 0; i < no_commands-1; i++) { pipe(fd[i]); } // 先fork所有子进程,不中途等待 for (i = 0; i < no_commands; i++) { if (i == 0) { execute_command(mycommands[i].comm, mycommands[i].no_args, mycommands[i].args, -1, fd[i][1]); } else if (i == no_commands -1 ) { execute_command(mycommands[i].comm, mycommands[i].no_args, mycommands[i].args, fd[i-1][0], -1); } else { execute_command(mycommands[i].comm, mycommands[i].no_args, mycommands[i].args, fd[i-1][0], fd[i][1]); } } // 修复:父进程关闭所有管道的读写端 for (i = 0; i < no_commands-1; i++) { close(fd[i][0]); close(fd[i][1]); } // 等待所有子进程退出 for (i = 0; i < no_commands; i++) { wait(NULL); } return; }
第二步:修复execute_command函数
int execute_command(char *command, int argc, char *args[], int fd_in, int fd_out) { int pid = fork(); if (pid == 0) { // 处理输入重定向 if (fd_in != -1) { dup2(fd_in, STDIN_FILENO); close(fd_in); // 重定向完成后关闭原文件描述符 } // 处理输出重定向 if (fd_out != -1) { dup2(fd_out, STDOUT_FILENO); close(fd_out); // 重定向完成后关闭原文件描述符 } // 统一执行命令 execvp(command, args); // exec执行失败直接退出子进程,避免逻辑混乱 perror("execvp error"); exit(EXIT_FAILURE); } else if (pid < 0) { perror("fork error"); return -1; } // 父进程关闭自己持有的管道文件描述符 if (fd_in != -1) close(fd_in); if (fd_out != -1) close(fd_out); return 0; }
内容的提问来源于stack exchange,提问作者Talha hassan
相关产品推荐
相关产品推荐

