You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
        }
    }   
}

关键修复点

  1. 父进程在第一个子进程结束后,立即关闭管道写端pipefds[1],此时管道所有写端都关闭,grep读到EOF后会正常退出。
  2. 父进程在等待第二个子进程前关闭管道读端pipefds[0],避免文件描述符泄漏。
  3. 给execlp添加错误处理,方便调试执行失败的情况。

内容的提问来源于stack exchange,提问作者Noone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 16:41:32