Linux下如何将mmap内存作为子进程标准输入输出并传递?
在Linux中用共享内存作为子进程的stdin/stdout实现
可行性结论
完全可以通过共享内存实现父进程与子进程的无管道/临时文件通信,将共享内存映射为子进程的标准输入和输出。但不能用std::system,因为它依赖shell执行命令,无法直接替换标准IO的文件描述符,必须用fork()+exec()系列函数手动完成重定向。
核心实现步骤
- 创建共享内存对象:用
shm_open创建两个共享内存对象(分别对应stdin和stdout),通过ftruncate设置大小,再用mmap映射到进程地址空间。 - 父进程预写入输入数据:往stdin对应的共享内存区域写入子进程需要的输入内容,注意调整文件指针到起始位置,确保子进程能从头读取。
- Fork子进程并重定向IO:
- 子进程中关闭原有的
STDIN_FILENO(0)和STDOUT_FILENO(1)。 - 用
dup2()将共享内存的文件描述符复制到0和1的位置,替换标准输入输出。 - 关闭不再需要的共享内存文件描述符,解除内存映射(可选,子进程退出后会自动清理)。
- 调用
exec()系列函数启动目标程序。
- 子进程中关闭原有的
- 父进程读取输出:等待子进程执行完毕后,直接从stdout对应的共享内存区域读取输出结果。
- 清理资源:解除内存映射,关闭文件描述符,用
shm_unlink删除共享内存对象,避免残留。
完整代码示例
#include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <cstring> #include <iostream> int main() { // 创建stdin共享内存 const char* shm_stdin_name = "/my_stdin"; int fd_stdin = shm_open(shm_stdin_name, O_CREAT | O_RDWR, 0666); if (fd_stdin == -1) { perror("shm_open stdin failed"); return 1; } size_t stdin_size = 100; if (ftruncate(fd_stdin, stdin_size) == -1) { perror("ftruncate stdin failed"); close(fd_stdin); shm_unlink(shm_stdin_name); return 1; } char* p_stdin = (char*)mmap(nullptr, stdin_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_stdin, 0); if (p_stdin == MAP_FAILED) { perror("mmap stdin failed"); close(fd_stdin); shm_unlink(shm_stdin_name); return 1; } // 创建stdout共享内存 const char* shm_stdout_name = "/my_stdout"; int fd_stdout = shm_open(shm_stdout_name, O_CREAT | O_RDWR, 0666); if (fd_stdout == -1) { perror("shm_open stdout failed"); munmap(p_stdin, stdin_size); close(fd_stdin); shm_unlink(shm_stdin_name); return 1; } size_t stdout_size = 200; if (ftruncate(fd_stdout, stdout_size) == -1) { perror("ftruncate stdout failed"); munmap(p_stdin, stdin_size); close(fd_stdin); close(fd_stdout); shm_unlink(shm_stdin_name); shm_unlink(shm_stdout_name); return 1; } char* p_stdout = (char*)mmap(nullptr, stdout_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_stdout, 0); if (p_stdout == MAP_FAILED) { perror("mmap stdout failed"); munmap(p_stdin, stdin_size); close(fd_stdin); close(fd_stdout); shm_unlink(shm_stdin_name); shm_unlink(shm_stdout_name); return 1; } // 父进程写入输入数据 const char* input = "Hello from parent!\n"; strncpy(p_stdin, input, stdin_size - 1); p_stdin[stdin_size - 1] = '\0'; lseek(fd_stdin, 0, SEEK_SET); // 重置文件指针到开头 // Fork子进程 pid_t pid = fork(); if (pid == -1) { perror("fork failed"); munmap(p_stdin, stdin_size); munmap(p_stdout, stdout_size); close(fd_stdin); close(fd_stdout); shm_unlink(shm_stdin_name); shm_unlink(shm_stdout_name); return 1; } else if (pid == 0) { // 子进程重定向stdin if (close(STDIN_FILENO) == -1) { perror("close stdin failed"); _exit(1); } if (dup2(fd_stdin, STDIN_FILENO) == -1) { perror("dup2 stdin failed"); _exit(1); } // 子进程重定向stdout if (close(STDOUT_FILENO) == -1) { perror("close stdout failed"); _exit(1); } if (dup2(fd_stdout, STDOUT_FILENO) == -1) { perror("dup2 stdout failed"); _exit(1); } // 清理子进程中不需要的资源 close(fd_stdin); close(fd_stdout); munmap(p_stdin, stdin_size); munmap(p_stdout, stdout_size); // 执行目标程序(示例用cat,读取stdin并输出到stdout) execlp("cat", "cat", nullptr); perror("execlp failed"); _exit(1); } else { // 父进程等待子进程完成 int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { std::cout << "子进程退出状态:" << WEXITSTATUS(status) << std::endl; } else { std::cout << "子进程异常终止" << std::endl; } // 读取子进程输出 std::cout << "子进程输出内容:\n" << p_stdout << std::endl; // 父进程清理资源 munmap(p_stdin, stdin_size); munmap(p_stdout, stdout_size); close(fd_stdin); close(fd_stdout); shm_unlink(shm_stdin_name); shm_unlink(shm_stdout_name); } return 0; }
关键注意事项
- 共享内存权限:
shm_open的mode参数要设置合理(如0666),确保子进程能访问;不过因为子进程继承父进程的文件描述符,只要父进程有读写权限即可。 - 内存同步:使用
MAP_SHARED标志确保父子进程对共享内存的修改互相可见。 - 资源清理:必须调用
shm_unlink删除共享内存对象,否则该对象会一直存在于系统中,直到重启或手动删除。 std::system的局限性:该函数会启动shell,无法直接将文件描述符重定向为标准IO,因此必须用fork()+exec()的组合。
内容的提问来源于stack exchange,提问作者Антон Сазонов
相关产品推荐
相关产品推荐

