使用pipe()和dup2()模拟Linux Shell管道时程序停滞的问题
管道操作停滞无输出的问题修复(不修改
do_command结构) 问题原因
程序停滞无输出的核心是主进程持有管道写端的额外未关闭引用:
- 主进程调用
dup2(pipefd[1], STDOUT_FILENO)后,自身标准输出被绑定到管道写端,但主进程并未关闭这个副本,也没有退出。 - 执行
echo的子进程退出后,主进程关闭了管道写端的原描述符pipefd[1],但主进程的STDOUT_FILENO仍是管道写端的副本,处于打开状态。 - 执行
rev的子进程从管道读端读取数据时,因为管道写端还有主进程的引用未关闭,无法收到EOF信号,会一直阻塞等待输入,导致程序停滞。
修复方案(不修改do_command结构)
不要在主进程中直接修改自身文件描述符,而是为每个命令单独创建二级子进程,在二级子进程内完成管道重定向,确保主进程不持有管道的任何引用。具体步骤:
- 创建管道后,fork子进程专门处理
echo:在该子进程内完成stdout到管道写端的重定向,关闭管道未使用描述符后调用do_command。 - 主进程关闭管道写端,再fork子进程处理
rev:在该子进程内完成stdin到管道读端的重定向,关闭管道读端后调用do_command。 - 主进程关闭管道读端,等待所有子进程退出。
修复后的代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int do_command(char* args[]) { int status; pid_t pid = fork(); if (pid == 0) { execvp(args[0], args); // execvp失败才会执行到这里 perror("execvp"); exit(EXIT_FAILURE); } else if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else { wait(&status); } return 0; } int main() { int pipefd[2]; pid_t pid_echo, pid_rev; // 创建管道 if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } // 处理echo命令的子进程 pid_echo = fork(); if (pid_echo == 0) { // 重定向stdout到管道写端 if (dup2(pipefd[1], STDOUT_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); } // 关闭管道的原始描述符(已通过dup2获得副本) close(pipefd[0]); close(pipefd[1]); char* echo[] = { "echo", "Hello, World!", NULL }; do_command(echo); exit(EXIT_SUCCESS); } else if (pid_echo == -1) { perror("fork echo"); exit(EXIT_FAILURE); } // 主进程关闭管道写端 close(pipefd[1]); // 处理rev命令的子进程 pid_rev = fork(); if (pid_rev == 0) { // 重定向stdin到管道读端 if (dup2(pipefd[0], STDIN_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); } // 关闭管道读端 close(pipefd[0]); char* rev[] = { "rev", NULL }; do_command(rev); exit(EXIT_SUCCESS); } else if (pid_rev == -1) { perror("fork rev"); exit(EXIT_FAILURE); } // 主进程关闭管道读端 close(pipefd[0]); // 等待两个子进程执行完成 waitpid(pid_echo, NULL, 0); waitpid(pid_rev, NULL, 0); return 0; }
扩展说明
- 修复后程序会正常输出反转后的字符串
!dlroW ,olleH,因为echo子进程退出后,管道写端的所有引用都会被关闭,rev子进程读取完数据后会收到EOF并正常退出。 - 保留了
do_command的原有结构,后续扩展多管道链时,只需重复"创建管道→fork子进程处理前序命令→关闭对应管道端→fork子进程处理后续命令"的逻辑即可。
内容的提问来源于stack exchange,提问作者MyBug18
相关产品推荐
相关产品推荐

