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

模拟bash管道时wc子进程挂起问题排查求助

管道挂起问题排查与修复

问题根源

你的代码导致wc进程挂起的核心原因是管道的引用计数未正确减少,使得下游进程一直等待输入EOF,具体有两点:

  1. 子进程未关闭冗余的管道描述符:dup2将管道复制到标准输入/输出后,原管道描述符仍处于打开状态,增加了管道的引用计数;
  2. 父进程关闭管道的时机滞后:fork完所有子进程后才关闭管道,导致上游管道的写端被父进程长时间持有,下游进程(如grep)无法及时收到EOF,进而阻塞wc。

修复后的代码

#include <stddef.h> 
#include <stdio.h>  
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

#define CMD_NUM 3

int main(void)
{
    pid_t pids[CMD_NUM]; 
    int pipeFds[CMD_NUM - 1][2]; 

    // 创建所有管道
    for (int i = 0; i < CMD_NUM - 1; i++) {
        if (pipe(pipeFds[i]) == -1) {
            perror("Error creating pipes");
            exit(EXIT_FAILURE);
        }
    }

    // 第一个子进程:ls
    pids[0] = fork();
    if (pids[0] == 0) {
        // 关闭不需要的读端
        close(pipeFds[0][0]);
        // 重定向stdout到管道写端
        dup2(pipeFds[0][1], STDOUT_FILENO);
        // 关闭冗余的管道写端描述符
        close(pipeFds[0][1]);

        char* argv[] = {"ls", "-l", NULL};
        execvp("ls", argv);
        perror("Error ls execvp");
        exit(EXIT_FAILURE);
    }
    // 父进程关闭该管道的写端(已交给ls子进程)
    close(pipeFds[0][1]);

    // 中间子进程:grep
    for (int i = 1; i < CMD_NUM - 1; i++) {
        pids[i] = fork();
        if (pids[i] == 0) {
            // 处理输入管道(前一个管道)
            close(pipeFds[i-1][1]);
            dup2(pipeFds[i-1][0], STDIN_FILENO);
            close(pipeFds[i-1][0]);

            // 处理输出管道(当前管道)
            close(pipeFds[i][0]);
            dup2(pipeFds[i][1], STDOUT_FILENO);
            close(pipeFds[i][1]);

            char* argv[] = {"grep", "ma", NULL};
            execvp("grep", argv);
            perror("Error in execvp");
            exit(EXIT_FAILURE);
        }
        // 父进程关闭该子进程使用的管道端
        close(pipeFds[i-1][0]);
        close(pipeFds[i][1]);
    }

    // 最后一个子进程:wc
    pids[CMD_NUM - 1] = fork();
    if (pids[CMD_NUM - 1] == 0) {
        int last_pipe_idx = CMD_NUM - 2;
        // 关闭不需要的写端
        close(pipeFds[last_pipe_idx][1]);
        // 重定向stdin到管道读端
        dup2(pipeFds[last_pipe_idx][0], STDIN_FILENO);
        // 关闭冗余的管道读端描述符
        close(pipeFds[last_pipe_idx][0]);

        char* argv[] = {"wc", NULL};
        execvp(argv[0], argv);
        perror("Error en execvp");
        exit(EXIT_FAILURE);
    }
    // 父进程关闭该管道的读端(已交给wc子进程)
    close(pipeFds[CMD_NUM - 2][0]);

    // 等待最后一个子进程结束
    waitpid(pids[CMD_NUM - 1], NULL, 0);

    return 0;
}

关键修复点说明

  • 子进程关闭冗余描述符:每次dup2完成后,立即关闭原管道描述符,确保管道的引用计数仅保留必要的读写端;
  • 父进程及时关闭管道端:fork完每个子进程后,父进程立即关闭该子进程已经接管的管道端,避免持有不必要的描述符导致下游进程阻塞;
  • 补充必要头文件:添加了<sys/wait.h>头文件,消除waitpid隐式声明的警告。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:25:49