在C程序中通过管道实现父进程控制execve创建的Bash子进程
嘿,这个需求本质上就是实现一个简单的父进程代理shell的功能,我给你写一个完整的可运行C代码,再把核心逻辑拆解开讲清楚,保证你能理解每个环节:
完整可运行代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #define BUF_SIZE 1024 int main() { int parent_to_child[2]; // 父进程写 → 子进程读:用来给Bash发命令 int child_to_parent[2]; // 子进程写 → 父进程读:用来接收Bash的输出 pid_t pid; char buf[BUF_SIZE]; ssize_t n_read; // 创建两个单向管道 if (pipe(parent_to_child) == -1 || pipe(child_to_parent) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } // 分叉创建子进程 pid = fork(); if (pid == -1) { perror("fork failed"); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程:启动Bash并绑定管道IO // 关闭子进程用不到的管道端(必须关,否则会导致死锁或资源泄漏) close(parent_to_child[1]); close(child_to_parent[0]); // 将Bash的标准输入重定向到父→子管道的读端 if (dup2(parent_to_child[0], STDIN_FILENO) == -1) { perror("dup2 stdin failed"); exit(EXIT_FAILURE); } // 将Bash的标准输出重定向到子→父管道的写端 if (dup2(child_to_parent[1], STDOUT_FILENO) == -1) { perror("dup2 stdout failed"); exit(EXIT_FAILURE); } // 可选:把标准错误也重定向过来,这样错误信息也能传回父进程 if (dup2(child_to_parent[1], STDERR_FILENO) == -1) { perror("dup2 stderr failed"); exit(EXIT_FAILURE); } // 重定向完成后,关闭原来的管道描述符(已经被dup2复制了,留着没用) close(parent_to_child[0]); close(child_to_parent[1]); // 调用execve启动Bash // 注意:execve需要绝对路径,参数数组必须以NULL结尾 char *args[] = {"/bin/bash", NULL}; execve("/bin/bash", args, NULL); // 如果走到这里,说明execve执行失败了 perror("execve failed"); exit(EXIT_FAILURE); } else { // 父进程:读取用户输入→发给Bash,接收Bash输出→打印到终端 // 关闭父进程用不到的管道端 close(parent_to_child[0]); close(child_to_parent[1]); printf("Enter command (type 'exit' to quit):\n"); while (1) { // 打印提示符并读取用户输入 printf("> "); fflush(stdout); // 强制刷新输出缓冲区,确保提示符及时显示 n_read = read(STDIN_FILENO, buf, BUF_SIZE); if (n_read == -1) { perror("read stdin failed"); break; } if (n_read == 0) { // 用户按下Ctrl+D,结束输入 break; } // 检查用户是否输入exit,是的话退出循环 if (strncmp(buf, "exit\n", 5) == 0 || strncmp(buf, "exit", 4) == 0) { break; } // 将输入的命令写入管道,发给Bash if (write(parent_to_child[1], buf, n_read) == -1) { perror("write to child failed"); break; } // 读取Bash的输出并打印到父进程的标准输出 memset(buf, 0, BUF_SIZE); while ((n_read = read(child_to_parent[0], buf, BUF_SIZE)) > 0) { write(STDOUT_FILENO, buf, n_read); memset(buf, 0, BUF_SIZE); } if (n_read == -1) { perror("read from child failed"); break; } } // 关闭管道,通知Bash结束 close(parent_to_child[1]); close(child_to_parent[0]); // 等待子进程退出,避免产生僵尸进程 waitpid(pid, NULL, 0); printf("\nChild Bash process exited.\n"); } return 0; }
核心逻辑拆解
- 管道设计:我们需要两个单向管道,而不是一个双向管道——因为进程间通信是单向流式的,一个管父发命令,一个管子回输出,这样逻辑更清晰,也避免冲突。
- IO重定向:子进程必须把Bash的标准输入/输出绑定到管道,否则Bash还是会默认从终端读、往终端写,达不到我们的控制目的。这里用
dup2系统调用完成重定向,它会把指定的文件描述符替换成目标描述符(比如把parent_to_child[0]替换成STDIN_FILENO)。 - execve注意事项:
execve要求传入程序的绝对路径(比如/bin/bash),不能直接写bash(那是execlp这类函数的功能);另外参数数组必须以NULL结尾,这是exec系列函数的要求。 - 死锁避免:一定要关闭不需要的管道端!比如父进程不需要读
parent_to_child的读端,就必须关掉它,否则系统会认为管道的读端还在打开,可能导致写端的进程一直等待。 - 退出处理:父进程退出前要关闭管道,这样Bash会因为输入流关闭而退出;然后用
waitpid等待子进程结束,防止僵尸进程残留。
内容的提问来源于stack exchange,提问作者Narc Eritor
相关产品推荐
相关产品推荐

