UNIX C中“反向mmap”实现:地址空间映射至文件系统无需写入磁盘
替代临时磁盘文件的内存共享方案
你要的“反向mmap”本质是让程序B能像读取普通文件一样访问父进程的内存空间,同时避免磁盘IO和临时文件残留。以下是几种实用技术方案:
1. 利用 /dev/shm 内存文件系统
/dev/shm 是基于内存的临时文件系统,写入这里的文件不会落到磁盘,重启后自动消失。父进程可以在这里创建文件并映射到自身地址空间,程序B直接读取该文件路径即可。
实现步骤:
- 父进程在
/dev/shm创建文件:int fd = open("/dev/shm/my_temp_file", O_RDWR | O_CREAT | O_EXCL, 0600); - 将文件映射到父进程内存:
char *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - 写入数据到
buf,然后通知程序B读取/dev/shm/my_temp_file - 程序B读取完成后,父进程删除文件:
unlink("/dev/shm/my_temp_file");
优点:
- 实现简单,程序B无需修改,直接读普通文件路径
- 无磁盘IO开销
缺点:
- 需要处理文件命名冲突问题
- 依赖系统挂载的
/dev/shm(大部分Linux系统默认挂载)
2. memfd_create + UNIX域套接字传递文件描述符
memfd_create 可以创建匿名的内存文件描述符(无文件系统路径),父进程将其映射到自身内存写入数据,再通过UNIX域套接字把这个fd传递给程序B,程序B直接读该fd即可,完全不需要文件系统路径。
代码示例(C语言):
#include <sys/memfd.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/wait.h> int main() { // 创建匿名内存文件 int memfd = memfd_create("shared_data", MFD_CLOEXEC); if (memfd == -1) { perror("memfd_create"); return 1; } const size_t data_size = 4096; // 扩展内存文件大小 if (ftruncate(memfd, data_size) == -1) { perror("ftruncate"); return 1; } // 映射到父进程地址空间,写入数据 char *mem_buf = mmap(NULL, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0); if (mem_buf == MAP_FAILED) { perror("mmap"); return 1; } strncpy(mem_buf, "Data from parent's address space", data_size); // 创建UNIX域套接字对,用于传递fd int sock_pair[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) == -1) { perror("socketpair"); return 1; } pid_t b_pid = fork(); if (b_pid == 0) { // 子进程模拟程序B:接收fd并读取数据 close(sock_pair[0]); int received_fd; char dummy; struct msghdr msg = {0}; struct iovec iov = {&dummy, 1}; msg.msg_iov = &iov; msg.msg_iovlen = 1; char ctrl_buf[CMSG_SPACE(sizeof(int))]; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); if (recvmsg(sock_pair[1], &msg, 0) == -1) { perror("recvmsg"); return 1; } struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); if (cmsg && cmsg->cmsg_type == SCM_RIGHTS) { received_fd = *(int *)CMSG_DATA(cmsg); char read_buf[data_size]; read(received_fd, read_buf, data_size); printf("Program B read: %s\n", read_buf); close(received_fd); } close(sock_pair[1]); return 0; } else { // 父进程:发送fd close(sock_pair[1]); struct msghdr msg = {0}; char dummy = 'a'; struct iovec iov = {&dummy, 1}; msg.msg_iov = &iov; msg.msg_iovlen = 1; char ctrl_buf[CMSG_SPACE(sizeof(int))]; msg.msg_control = ctrl_buf; msg.msg_controllen = sizeof(ctrl_buf); struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cmsg) = memfd; if (sendmsg(sock_pair[0], &msg, 0) == -1) { perror("sendmsg"); return 1; } waitpid(b_pid, NULL, 0); close(memfd); close(sock_pair[0]); munmap(mem_buf, data_size); return 0; } }
优点:
- 完全无文件系统痕迹,不会残留临时文件
- 效率最高,直接在内存间传递数据
- 避免命名冲突
缺点:
- 程序B需要支持通过UNIX套接字接收文件描述符,或者由父进程fork启动程序B
3. 共享内存 + FUSE 用户态文件系统
如果必须让程序B通过普通文件路径访问父进程内存,可以用FUSE实现一个极简的用户态文件系统,将父进程的共享内存段映射为文件。程序B读取该文件时,FUSE内核模块会直接从共享内存读取数据。
实现思路:
- 父进程创建共享内存段:
int shm_fd = shm_open("/my_shm", O_RDWR | O_CREAT, 0600);,并映射到自身内存写入数据 - 编写FUSE文件系统代码,将
/my_fuse_mount/data路径关联到该共享内存段 - 挂载FUSE文件系统到某个目录,程序B直接读取
/my_fuse_mount/data即可 - 程序B完成后,卸载FUSE文件系统并删除共享内存段
优点:
- 程序B无需任何修改,完全像读普通文件一样访问
- 完全避免磁盘IO
缺点:
- 需要编写FUSE文件系统代码,实现复杂度较高
- 依赖FUSE内核模块(大部分Linux系统默认支持)
内容的提问来源于stack exchange,提问作者user22925547
相关产品推荐
相关产品推荐

