如何从shm_open获取内存地址?fork进程共享内存mmap地址一致实现
关于fork后共享内存地址一致的问题解答
嘿,我来帮你把这个问题捋清楚!首先明确说:用shm_open完全能实现父子进程的内存共享,根本不用换成shmget——shmget是System V那套老接口,shm_open属于POSIX标准的新接口,用法更贴近文件操作,反而更推荐用它。
核心问题:怎么让mmap返回相同地址
你遇到的mmap地址不同,大概率是因为父子进程各自单独调用了mmap。其实有两种简单的方式解决这个问题:
方式一:父进程先映射,再fork(最简单,自动地址一致)
fork之后,子进程会继承父进程的所有内存映射,包括共享内存的映射地址。也就是说,父进程先完成共享内存的mmap,再fork子进程,子进程直接用父进程的映射地址就行,根本不需要自己再调用mmap。
给你改好的最简示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/wait.h> #define SHM_NAME "/my_shared_mem" #define SHM_SIZE 4096 int main() { // 创建/打开共享内存对象 int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } // 设置共享内存的大小 if (ftruncate(shm_fd, SHM_SIZE) == -1) { perror("ftruncate failed"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } // 父进程先映射共享内存 void *shared_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); if (shared_addr == MAP_FAILED) { perror("mmap failed"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } printf("父进程映射地址: %p\n", shared_addr); // 写入测试数据 strcpy((char*)shared_addr, "Hello from parent!"); pid_t pid = fork(); if (pid == -1) { perror("fork failed"); munmap(shared_addr, SHM_SIZE); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程直接用继承的映射地址,无需重新mmap printf("子进程映射地址: %p\n", shared_addr); printf("子进程读取到的数据: %s\n", (char*)shared_addr); // 修改数据,父进程能看到变化 strcpy((char*)shared_addr, "Hello from child!"); munmap(shared_addr, SHM_SIZE); close(shm_fd); exit(EXIT_SUCCESS); } else { // 父进程等待子进程结束 wait(NULL); printf("父进程读取到修改后的数据: %s\n", (char*)shared_addr); // 清理资源 munmap(shared_addr, SHM_SIZE); close(shm_fd); shm_unlink(SHM_NAME); // 删除共享内存对象,避免残留 exit(EXIT_SUCCESS); } }
方式二:用MAP_FIXED强制指定相同地址(适合特殊场景)
如果因为某些原因,你必须让父子进程各自调用mmap,那可以用MAP_FIXED标志强制指定同一个地址。步骤是:
- 父进程先拿到一个可用地址(比如临时申请匿名内存再释放)
- fork后,子进程用这个地址+
MAP_FIXED去映射同一个共享内存对象
⚠️ 注意:MAP_FIXED是强操作——如果指定的地址已经被其他映射占用,内核会直接覆盖原来的映射,可能导致程序崩溃,所以一定要先确保地址空闲。
示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/wait.h> #define SHM_NAME "/my_shared_mem_fixed" #define SHM_SIZE 4096 int main() { int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } if (ftruncate(shm_fd, SHM_SIZE) == -1) { perror("ftruncate failed"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } // 先申请临时匿名内存,拿到一个空闲地址 void *target_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (target_addr == MAP_FAILED) { perror("temp mmap failed"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } munmap(target_addr, SHM_SIZE); // 释放临时地址,确保它空闲 // 父进程用指定地址映射共享内存 void *parent_addr = mmap(target_addr, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, shm_fd, 0); if (parent_addr == MAP_FAILED) { perror("parent mmap with MAP_FIXED failed"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } printf("父进程映射地址: %p\n", parent_addr); strcpy((char*)parent_addr, "Parent's initial data"); pid_t pid = fork(); if (pid == -1) { perror("fork failed"); munmap(parent_addr, SHM_SIZE); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程用同一个地址+MAP_FIXED映射 void *child_addr = mmap(target_addr, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, shm_fd, 0); if (child_addr == MAP_FAILED) { perror("child mmap with MAP_FIXED failed"); close(shm_fd); exit(EXIT_FAILURE); } printf("子进程映射地址: %p\n", child_addr); printf("子进程读取数据: %s\n", (char*)child_addr); strcpy((char*)child_addr, "Child modified data"); munmap(child_addr, SHM_SIZE); close(shm_fd); exit(EXIT_SUCCESS); } else { wait(NULL); printf("父进程读取修改后的数据: %s\n", (char*)parent_addr); munmap(parent_addr, SHM_SIZE); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_SUCCESS); } }
额外问题解答
- 能不能用shm_open实现共享内存? 当然可以!shm_open是POSIX标准的共享内存接口,通过文件描述符操作,和普通文件用法类似,比shmget更灵活易用,完全能满足你的需求。
- 必须改用shmget吗? 完全不需要,shmget是老旧的System V接口,现在更推荐使用POSIX的shm_open+mmap组合。
内容的提问来源于stack exchange,提问作者Igor Liferenko
相关产品推荐
相关产品推荐

