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

通过execvp执行sed导致其他管道阻塞的问题排查

问题解答

a) 阻塞原因分析

不是因为两个子进程共享STDIN。每个通过fork()创建的子进程会复制父进程的文件描述符表,但后续通过dup2()将各自管道的读端替换为自身的STDIN_FILENO,所以两个子进程的STDIN是完全独立的。

阻塞的核心原因是未关闭所有未使用的管道文件描述符:

  • 父进程创建多个管道后,每个子进程会继承所有管道的文件描述符。如果子进程没有关闭与自身无关的管道fd(比如sed子进程继承了sort管道的写端却没关闭),或者父进程没有关闭不需要的管道fd,会导致sort的输入管道的写入端引用计数不为0。
  • sort进程会一直等待输入,直到其STDIN对应的管道写入端全部关闭。只要还有一个写入端处于打开状态,sort就认为可能还有数据会进来,不会结束进程,父进程读取sort输出的管道就会一直阻塞。

b) 启动N个STDIN独立子进程的标准流程

要确保每个子进程的STDIN完全独立,核心是为每个子进程单独创建管道,并严格管理所有管道文件描述符的生命周期,步骤如下:

  1. 为每个子进程创建独立管道:调用pipe()为每个子进程生成一对管道(父进程写管道的一端,子进程读另一端作为STDIN)。
  2. fork创建子进程并配置STDIN:
    • 子进程中,先关闭管道的写入端(子进程只需要读)。
    • 用dup2(pipe_read_fd, STDIN_FILENO)将管道读端绑定到STDIN。
    • 关闭原管道读端(dup2会保留原fd,必须手动关闭避免资源泄漏)。
    • 关闭所有与当前子进程无关的管道文件描述符(继承自父进程的其他管道fd都要关掉)。
    • 调用execvp()启动目标程序。
  3. 父进程管理管道:
    • 关闭每个管道的读端(父进程只需要写数据给子进程)。
    • 保存每个管道的写端,用于后续给对应子进程发送数据。
    • 给子进程发送完数据后,立即关闭对应管道的写端,让子进程知道输入结束。
    • 最后调用waitpid()等待所有子进程结束,避免僵尸进程。

修正后的测试代码示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define BUF_SIZE 1024

int main() {
    // Sort的输入/输出管道(父写子读、子写父读)
    int sort_in[2];
    int sort_out[2];
    // Sed的输入管道(父写子读)
    int sed_in[2];

    if (pipe(sort_in) == -1) { perror("pipe sort_in"); exit(1); }
    if (pipe(sort_out) == -1) { perror("pipe sort_out"); exit(1); }
    if (pipe(sed_in) == -1) { perror("pipe sed_in"); exit(1); }

    // 启动sort子进程
    pid_t sort_pid = fork();
    if (sort_pid == -1) { perror("fork sort"); exit(1); }
    if (sort_pid == 0) {
        // 绑定STDIN到sort输入管道读端
        close(sort_in[1]);
        dup2(sort_in[0], STDIN_FILENO);
        close(sort_in[0]);
        // 绑定STDOUT到sort输出管道写端
        close(sort_out[0]);
        dup2(sort_out[1], STDOUT_FILENO);
        close(sort_out[1]);
        // 关闭无关管道fd
        close(sed_in[0]);
        close(sed_in[1]);
        // 启动sort
        execlp("sort", "sort", NULL);
        perror("execlp sort");
        exit(1);
    }

    // 启动sed子进程
    pid_t sed_pid = fork();
    if (sed_pid == -1) { perror("fork sed"); exit(1); }
    if (sed_pid == 0) {
        // 绑定STDIN到sed输入管道读端
        close(sed_in[1]);
        dup2(sed_in[0], STDIN_FILENO);
        close(sed_in[0]);
        // 关闭无关管道fd
        close(sort_in[0]);
        close(sort_in[1]);
        close(sort_out[0]);
        close(sort_out[1]);
        // 启动sed(输出到终端)
        execlp("sed", "sed", "s/a/x/g", NULL);
        perror("execlp sed");
        exit(1);
    }

    // 父进程关闭无需使用的管道端
    close(sort_in[0]);
    close(sort_out[1]);
    close(sed_in[0]);

    // 给sort发送测试数据并关闭写端
    const char* sort_data = "banana\napple\ncherry\n";
    write(sort_in[1], sort_data, sizeof(sort_data)-1);
    close(sort_in[1]);

    // 给sed发送测试数据并关闭写端
    const char* sed_data = "apple banana cherry\n";
    write(sed_in[1], sed_data, sizeof(sed_data)-1);
    close(sed_in[1]);

    // 读取并打印sort的输出
    char buf[BUF_SIZE];
    ssize_t bytes_read;
    printf("Sort output:\n");
    while ((bytes_read = read(sort_out[0], buf, BUF_SIZE-1)) > 0) {
        buf[bytes_read] = '\0';
        printf("%s", buf);
    }
    close(sort_out[0]);

    // 等待子进程退出
    waitpid(sort_pid, NULL, 0);
    waitpid(sed_pid, NULL, 0);

    return 0;
}

修正后现象说明

运行代码后,父进程会正常读取到sort的排序结果,同时sed也会输出处理后的内容,不会出现阻塞。核心是严格关闭了所有未使用的管道文件描述符,确保sort的输入管道写入端全部关闭,sort进程能正常结束并输出结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:05:15