通过execvp执行sed导致其他管道阻塞的问题排查
问题解答
a) 阻塞原因分析
不是因为两个子进程共享STDIN。每个通过fork()创建的子进程会复制父进程的文件描述符表,但后续通过dup2()将各自管道的读端替换为自身的STDIN_FILENO,所以两个子进程的STDIN是完全独立的。
阻塞的核心原因是未关闭所有未使用的管道文件描述符:
- 父进程创建多个管道后,每个子进程会继承所有管道的文件描述符。如果子进程没有关闭与自身无关的管道fd(比如sed子进程继承了sort管道的写端却没关闭),或者父进程没有关闭不需要的管道fd,会导致sort的输入管道的写入端引用计数不为0。
sort进程会一直等待输入,直到其STDIN对应的管道写入端全部关闭。只要还有一个写入端处于打开状态,sort就认为可能还有数据会进来,不会结束进程,父进程读取sort输出的管道就会一直阻塞。
b) 启动N个STDIN独立子进程的标准流程
要确保每个子进程的STDIN完全独立,核心是为每个子进程单独创建管道,并严格管理所有管道文件描述符的生命周期,步骤如下:
- 为每个子进程创建独立管道:调用
pipe()为每个子进程生成一对管道(父进程写管道的一端,子进程读另一端作为STDIN)。 - fork创建子进程并配置STDIN:
- 子进程中,先关闭管道的写入端(子进程只需要读)。
- 用
dup2(pipe_read_fd, STDIN_FILENO)将管道读端绑定到STDIN。 - 关闭原管道读端(
dup2会保留原fd,必须手动关闭避免资源泄漏)。 - 关闭所有与当前子进程无关的管道文件描述符(继承自父进程的其他管道fd都要关掉)。
- 调用
execvp()启动目标程序。
- 父进程管理管道:
- 关闭每个管道的读端(父进程只需要写数据给子进程)。
- 保存每个管道的写端,用于后续给对应子进程发送数据。
- 给子进程发送完数据后,立即关闭对应管道的写端,让子进程知道输入结束。
- 最后调用
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
相关产品推荐
相关产品推荐

