自定义shell实现pipe与execvp时出现Bad file descriptors错误求助
自定义Shell管道实现错误修复方案
核心错误原因
- 重定向逻辑完全错误:
dup()会返回新分配的文件描述符编号,你调用dup(p[1])后立刻关闭了这个新fd,等于管道端没有被保留到标准输入/输出的位置,程序访问fd 0/1时指向已关闭的文件,所以报坏文件描述符错误。 - 进程调度逻辑错误:等待子进程1完全执行结束再fork子进程2,管道没有读端存在时,子进程1写入管道会触发
SIGPIPE信号直接报错,且管道缓冲区有限,大输出场景下会直接死锁。 - 使用
fclose()操作标准流不合理:底层文件描述符重定向应该直接用系统调用close()操作fd 0/1,避免stdio层缓冲的额外影响。
修复步骤
- 子进程1(执行
ls -l)重定向逻辑修正- 先关闭子进程不需要的管道读端
p[0] - 直接关闭标准输出对应的文件描述符
1 - 调用
dup2(p[1], 1),强制把管道写端复制到fd 1的位置,完成标准输出到管道的映射 - 关闭原管道写端
p[1],避免多余的文件描述符引用
- 先关闭子进程不需要的管道读端
- 子进程2(执行
tail -n 2)重定向逻辑修正- 先关闭子进程不需要的管道写端
p[1] - 直接关闭标准输入对应的文件描述符
0 - 调用
dup2(p[0], 0),强制把管道读端复制到fd 0的位置,完成标准输入到管道的映射 - 关闭原管道读端
p[0],避免多余的文件描述符引用
- 先关闭子进程不需要的管道写端
- 父进程逻辑修正
- 连续fork两个子进程,不要等待第一个子进程结束再fork第二个
- 父进程立刻关闭自己持有的管道两端
p[0]和p[1],避免管道无法正常结束 - 最后等待两个子进程执行完成即可
修正后完整代码
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include <stdlib.h> int main (int argc, char * argv[]){ int debugMode=0; int p[2]; int status; if (argc > 1 && strcmp(argv[1],"-d")==0) debugMode=1; if (pipe(p)<0) { perror("pipe create failed"); return 1; } // 创建第一个子进程 int child1= fork(); if (child1 == 0) { if (debugMode == 1) fprintf(stderr, "Child 1 is redirecting stdout to write end of pipe.\n"); // 关闭不需要的读端 close(p[0]); // 关闭标准输出 close(1); // 把管道写端复制到fd 1 dup2(p[1], 1); // 关闭原管道写端 close(p[1]); char *args[] = {"ls","-l",NULL}; if (execvp(args[0],args)<0){ if (debugMode ==1) perror("ls -l failed "); exit(1); } } // 直接创建第二个子进程,无需等待第一个结束 int child2 = fork(); if (child2 == 0) { if (debugMode == 1) fprintf(stderr, "Child 2 is redirecting stdin to read end of pipe.\n"); // 关闭不需要的写端 close(p[1]); // 关闭标准输入 close(0); // 把管道读端复制到fd 0 dup2(p[0], 0); // 关闭原管道读端 close(p[0]); char *args[] = {"tail","-n","2",NULL}; if (execvp(args[0],args)<0){ if (debugMode ==1) perror("tail -n 2 failed "); exit(1); } } // 父进程逻辑 if (debugMode == 1) fprintf(stderr, "Parent process is closing both ends of pipe.\n"); // 父进程关闭自己持有的管道两端,必须操作否则管道无法正常终止 close(p[0]); close(p[1]); // 等待两个子进程执行完成 waitpid(child1, &status, 0); waitpid(child2, &status, 0); if (debugMode == 1) fprintf(stderr, "All child processes terminated.\n"); return 0; }
额外说明
上述实现完全符合关闭标准输入输出的作业要求:两个子进程分别显式关闭了标准输出fd 1和标准输入fd 0,再将管道端映射到对应fd位置,完成命令的管道级联执行。
内容的提问来源于stack exchange,提问作者OmerLerner
相关产品推荐
相关产品推荐

