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

基于Linux的C语言多进程管道Shell开发通信问题求助

多管道Shell的进程通信问题

我正在开发一款支持多管道进程处理的C语言Shell,它会根据用户输入的、以|分隔的进程数量,为每个进程创建子进程。

示例:

texto1.txt 内容为:"Isto é o Texto 1"

$ cat texto1.txt | grep -c Isto

结果:1

但我在子进程间以及子进程与父进程的通信方面遇到了问题。

以下是我当前实现管道处理的execpipe函数代码,其中argv1和argv2为用户输入的两个独立进程(例如:ls -l | wc -l):

int execpipe(char ** argv1, char  ** argv2)
{
    int fds[2];
    pipe(fds);
    int i;
    pid_t p1, p2;
    p1 = fork();
    if (p1 == -1)
    { // error
        char * error = strerror(errno);
        printf("error fork!!\n");
        return 1;
    }
    if (p1 == 0)
    { // child process

        close(fds[0]);
        dup2(fds[1], STDOUT_FILENO);
        close(fds[1]);
        if(execvp(argv1[0], argv1)<0){ // run command AFTER pipe character in userinput
            char * error = strerror(errno);
            printf("unknown command\n");
            return 0;
        }
    }
    else
    { // parent process
        p2 = fork();

        if(p2==0){
            close(fds[1]);
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
            if(execvp(argv2[0], argv2)<0){ // run command AFTER pipe character in userinput
                char * error = strerror(errno);
                printf("unknown command\n");
                return 0;
            }
        }else{ //Parent waits for both of it's children
            wait(NULL);
            wait(NULL);
        }
    }
}

问题分析与修正方案

1. 父进程未关闭管道文件描述符

原代码中父进程创建管道后,没有关闭fds[0]和fds[1]。这会导致第二个子进程(处理argv2)的read操作无法检测到EOF——因为管道的写端还被父进程持有,子进程会一直阻塞等待数据。

修正:在父进程fork出两个子进程后,立即关闭管道的两端:

else{ //Parent waits for both of it's children
    close(fds[0]);
    close(fds[1]);
    wait(NULL);
    wait(NULL);
}

2. 错误处理不规范

  • execvp失败时,错误信息应该输出到stderr而非stdout,符合Shell的错误输出规范。
  • 子进程执行失败后应该退出错误码(比如exit(1)),而不是return 0,这样父进程可以获取子进程的退出状态。

修正子进程中的错误处理:

if(execvp(argv1[0], argv1) < 0){
    fprintf(stderr, "unknown command: %s\n", strerror(errno));
    exit(1);
}

3. 注释错误

第一个子进程的注释标注为“run command AFTER pipe”,但实际argv1是管道左侧的命令(比如示例中的cat texto1.txt),应该修正注释避免混淆。

4. 扩展多管道的思路

当前代码仅支持两个命令的管道,要支持任意数量的管道,可以采用循环的方式:

  • 维护一个“输入管道”的文件描述符,初始为STDIN_FILENO。
  • 对每个命令(除了最后一个),创建新的管道,fork子进程将输入重定向到当前输入管道,输出重定向到新管道,然后更新输入管道为新管道的读端。
  • 最后一个命令直接将输入重定向到之前的输入管道,执行即可。
  • 父进程需要关闭所有未使用的管道描述符,并等待所有子进程。

修正后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int execpipe(char ** argv1, char ** argv2)
{
    int fds[2];
    if (pipe(fds) == -1) {
        fprintf(stderr, "pipe error: %s\n", strerror(errno));
        return 1;
    }

    pid_t p1 = fork();
    if (p1 == -1) {
        fprintf(stderr, "fork error: %s\n", strerror(errno));
        close(fds[0]);
        close(fds[1]);
        return 1;
    }

    if (p1 == 0) {
        // 子进程1:执行管道左侧命令,输出到管道
        close(fds[0]);
        dup2(fds[1], STDOUT_FILENO);
        close(fds[1]);

        if (execvp(argv1[0], argv1) < 0) {
            fprintf(stderr, "unknown command: %s\n", strerror(errno));
            exit(1);
        }
    }

    pid_t p2 = fork();
    if (p2 == -1) {
        fprintf(stderr, "fork error: %s\n", strerror(errno));
        close(fds[0]);
        close(fds[1]);
        kill(p1, SIGTERM);
        wait(NULL);
        return 1;
    }

    if (p2 == 0) {
        // 子进程2:执行管道右侧命令,从管道读取输入
        close(fds[1]);
        dup2(fds[0], STDIN_FILENO);
        close(fds[0]);

        if (execvp(argv2[0], argv2) < 0) {
            fprintf(stderr, "unknown command: %s\n", strerror(errno));
            exit(1);
        }
    }

    // 父进程关闭管道两端,避免子进程阻塞
    close(fds[0]);
    close(fds[1]);

    // 等待两个子进程结束
    waitpid(p1, NULL, 0);
    waitpid(p2, NULL, 0);

    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:45:46