编译运行./prog ls ls wc 1.txt时程序停滞,需按Ctrl-D才完成的原因
程序执行停滞的原因分析与修复
你遇到的这个问题其实是管道使用中的典型陷阱——咱们先把场景和问题拆解清楚,再讲怎么解决。
首先看你的执行需求:运行./prog ls ls wc 1.txt,想要实现(ls; ls) | wc > 1.txt的效果,但程序卡住不动,必须按Control-D才能完成。先看看你的核心代码逻辑(虽然原代码有截断,但关键问题点已经能看出来):
#include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main(int argc, char const *argv[]) { int fd[2]; pipe(fd); if (!fork()) { close(fd[0]); dup2(fd[1], 1); close(fd[1]); if (!(fork())) { execlp(argv[1], argv[1], NULL); // 原代码截断,但核心逻辑是要执行两个ls } // 第一个子进程(P1)的后续逻辑缺失 } else { close(fd[1]); dup2(fd[0], 0); close(fd[0]); int fd_file = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0644); dup2(fd_file, 1); close(fd_file); execlp(argv[3], argv[3], NULL); } wait(NULL); return 0; }
为什么程序会停滞?
核心原因是管道的写端没有被完全关闭,导致wc进程一直阻塞等待输入。
咱们一步步理执行流程:
- 主进程创建管道,fork出第一个子进程P1。
- P1关闭管道读端,把标准输出重定向到管道写端,接着又fork出子进程P2来执行第一个
ls。 - 问题来了:P1在fork出P2之后,既没有执行第二个
ls,也没有退出,更没有主动关闭管道写端——它就这么挂着,一直占着管道的写端。 - 主进程这边已经把标准输入重定向到管道读端,执行
wc命令。而wc的特性是:会持续读取输入,直到所有管道写端都被关闭,才会停止读取并处理输出。
现在P2执行完ls退出后,P1还活着,并且握着管道写端没放,wc就会一直等着,以为还会有数据从管道过来。直到你按Control-D(手动关闭主进程的标准输入),或者P1关闭写端/退出,wc才会结束。
怎么修复?
你需要确保所有不需要管道写端的进程都关闭它,同时让P1正确完成第二个ls的执行后退出,自动释放管道写端。修改后的核心代码如下:
#include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main(int argc, char const *argv[]) { if (argc != 5) { fprintf(stderr, "Usage: %s cmd1 cmd2 out_cmd out_file\n", argv[0]); exit(1); } int fd[2]; if (pipe(fd) == -1) { perror("pipe failed"); exit(1); } pid_t pid1 = fork(); if (pid1 == -1) { perror("fork pid1 failed"); exit(1); } if (pid1 == 0) { // 子进程P1:负责执行两个ls,输出到管道 close(fd[0]); // 关闭读端,不需要 if (dup2(fd[1], STDOUT_FILENO) == -1) { perror("dup2 stdout failed"); exit(1); } close(fd[1]); // 重定向完成后关闭原写端 // 执行第一个ls pid_t pid2 = fork(); if (pid2 == -1) { perror("fork pid2 failed"); exit(1); } if (pid2 == 0) { execlp(argv[1], argv[1], NULL); perror("execlp cmd1 failed"); exit(1); } // 等待第一个ls执行完成 wait(NULL); // 执行第二个ls execlp(argv[2], argv[2], NULL); perror("execlp cmd2 failed"); exit(1); } else { // 主进程:负责执行wc,输出到文件 close(fd[1]); // 关闭写端,不需要 if (dup2(fd[0], STDIN_FILENO) == -1) { perror("dup2 stdin failed"); exit(1); } close(fd[0]); // 重定向完成后关闭原读端 // 打开输出文件 int fd_file = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd_file == -1) { perror("open out file failed"); exit(1); } if (dup2(fd_file, STDOUT_FILENO) == -1) { perror("dup2 stdout to file failed"); exit(1); } close(fd_file); // 执行wc execlp(argv[3], argv[3], NULL); perror("execlp out_cmd failed"); exit(1); } wait(NULL); return 0; }
修复后的关键变化:
- P1在fork出P2后,等待P2执行完第一个
ls,然后自己执行第二个ls,执行完成后自动退出,内核会自动关闭它持有的管道写端。 - 所有进程在完成管道重定向后,都主动关闭了原管道描述符,避免资源泄漏。
- 增加了错误处理,让程序更健壮。
这样修改后,当两个ls都执行完成退出后,管道的所有写端都被关闭,wc就会停止等待输入,处理完数据后退出,程序就不会再停滞了。
内容的提问来源于stack exchange,提问作者Akhmad Sumekenov
相关产品推荐
相关产品推荐

