C语言实现Linux Shell管道时子进程阻塞无法终止的问题排查
管道子进程阻塞问题分析
问题描述
我用C语言实现Linux Shell的管道功能,让第一个子进程执行cat try.txt的输出传给第二个子进程执行grep a,预期输出匹配行后程序正常结束,但实际第二个子进程输出正确结果后却阻塞无法终止,父进程等待它结束时陷入永久阻塞。
问题代码
/* pipe4.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int agrc, char* agrv[]) { int pipefds[2]; pid_t pid; pid_t pid2; int status; if(pipe(pipefds) == -1){ perror("pipe"); exit(EXIT_FAILURE); } pid = fork(); if(pid == -1){ perror("fork"); exit(EXIT_FAILURE); } if(pid == 0){ //replace stdout with the write end of the pipe dup2(pipefds[1],STDOUT_FILENO); //close read to pipe, in child close(pipefds[0]); execlp("cat","cat","try.txt",NULL); }else{ waitpid(pid, &status, 0); printf("first child done\n"); pid2 = fork(); if(pid2 == 0){ printf("second child start\n"); dup2(pipefds[0],STDIN_FILENO); close(pipefds[1]); execlp("grep","grep","a",NULL); } else{ waitpid(pid2, &status, 0); printf("second child end\n"); close(pipefds[0]); close(pipefds[1]); exit(EXIT_SUCCESS); printf("end\n"); } } }
原因分析
核心问题是父进程未及时关闭管道的写端,导致第二个子进程的grep命令一直阻塞等待输入:
- 第一个子进程执行
cat后会关闭自己持有的管道写端,但父进程还保留着管道的写端和读端。 grep从管道读取数据时,只要管道还有任意一个写端处于打开状态,它就会认为可能还有数据要过来,不会停止等待。- 父进程卡在
waitpid(pid2, &status, 0)等待子进程结束,而子进程因为管道写端未完全关闭一直不退出,形成死锁。
修复方案
父进程需要在第一个子进程结束后、启动第二个子进程前,立即关闭管道的写端;同时在等待第二个子进程前关闭读端,避免资源泄漏。修改后的代码如下:
/* 修复后的pipe4.c */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char* argv[]) { int pipefds[2]; pid_t pid; pid_t pid2; int status; if(pipe(pipefds) == -1){ perror("pipe"); exit(EXIT_FAILURE); } pid = fork(); if(pid == -1){ perror("fork"); exit(EXIT_FAILURE); } if(pid == 0){ // 将标准输出替换为管道写端 dup2(pipefds[1], STDOUT_FILENO); // 关闭子进程不需要的管道读端 close(pipefds[0]); execlp("cat", "cat", "try.txt", NULL); // execlp执行失败才会走到这里 perror("execlp cat failed"); exit(EXIT_FAILURE); } else { // 父进程不需要管道写端,立即关闭 close(pipefds[1]); waitpid(pid, &status, 0); printf("first child done\n"); pid2 = fork(); if(pid2 == 0){ printf("second child start\n"); dup2(pipefds[0], STDIN_FILENO); // 子进程不需要管道读端(已通过dup2替换) close(pipefds[0]); execlp("grep", "grep", "a", NULL); perror("execlp grep failed"); exit(EXIT_FAILURE); } else { // 父进程不需要管道读端了,关闭它 close(pipefds[0]); waitpid(pid2, &status, 0); printf("second child end\n"); exit(EXIT_SUCCESS); } } }
关键修复点
- 父进程在第一个子进程结束后,立即关闭管道写端
pipefds[1],此时管道所有写端都关闭,grep读到EOF后会正常退出。 - 父进程在等待第二个子进程前关闭管道读端
pipefds[0],避免文件描述符泄漏。 - 给
execlp添加错误处理,方便调试执行失败的情况。
内容的提问来源于stack exchange,提问作者Noone
相关产品推荐
相关产品推荐

