mmap 多进程共享必须用 MAP_SHARED,因其确保所有进程映射同一物理页并同步回文件;MAP_PRIVATE 为写时复制,修改不共享。需 O_RDWR 打开、ftruncate 预设大小,并配合适当同步机制。

为什么 mmap 在多进程共享中必须用 MAP_SHARED 而不是 MAP_PRIVATE
因为 MAP_PRIVATE 创建的是写时复制(COW)映射,子进程或其它进程对映射区域的修改不会反映到文件,也不会被其他进程看到——它根本不是共享的。只有 MAP_SHARED 才保证所有映射该文件的进程看到同一份物理内存页,且修改会同步回文件(进而被其它进程通过映射感知)。
常见错误现象:mmap 后写入数据,另一进程读出来全是初始值或随机垃圾;strace 看到 mmap 返回成功,但 msync 失败或被忽略。
- 必须用
open(..., O_RDWR)打开文件,只读打开无法映射为可写MAP_SHARED - 文件需预先用
ftruncate设置足够大小,否则写越界触发SIGBUS - Linux 下若挂载文件系统为
noexec或nosuid一般不影响,但nodev不影响,真正要防的是mand(强制访问控制)或 SELinux 限制memprotect类型策略
如何避免多个进程 mmap 同一文件时的竞态和初始化撕裂
文件映射本身不提供同步机制。如果进程 A 还没写完结构体头部,进程 B 就开始读,就会读到半初始化状态——这不是 mmap 的 bug,而是你没加同步。
典型使用场景:一个 producer 进程写入数据块 + 更新 ready_flag 字段,多个 consumer 进程轮询等待就绪。
- 不要用普通变量做标志位,必须是
std::atomic_int或volatile sig_atomic_t并配合memory_order_seq_cst(C++11+) - 更可靠的做法是用
pthread_mutex_t存在共享内存里,但需用PTHREAD_PROCESS_SHARED初始化,且所有进程必须在 mmap 后调用pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) - 简单方案:用
sem_open("/myshm", O_CREAT, 0644, 0)配合命名信号量,比手搓自旋更省 CPU
mmap 映射普通文件 vs /dev/shm 的实际性能差异在哪
映射 /dev/shm/mydata(tmpfs)通常比映射磁盘文件快,但差别不在“是否落盘”,而在于页回收行为和锁竞争。
关键差异点:
- tmpfs 文件不经过 block layer,缺页时直接从内存分配页,无 I/O 调度延迟
- 普通文件 mmap 在脏页回写时可能阻塞(尤其 ext4 默认
data=ordered),而 tmpfs 回写即释放,无持久化负担 - 但 tmpfs 受
shmmax和shmall内核参数限制,超限会mmap失败并返回ENOMEM - 注意:
/dev/shm下文件仍需ftruncate,且生命周期独立于进程——不显式shm_unlink,重启前一直占内存
为什么 fork 后子进程能直接用父进程的 mmap 地址,但 execve 后就失效了
因为 mmap 映射是 per-process 的虚拟地址空间条目,fork 会复制整个 vma(虚拟内存区域)结构,且标记为 MAP_SHARED 时底层物理页是共享的;而 execve 会清空当前进程全部 vma,加载新程序镜像,旧映射彻底消失。
容易踩的坑:
- 误以为
fork出来的子进程“继承”了映射就能长期稳定通信——其实父子进程间通信没问题,但一旦子进程execve(比如调用system("xxx")),映射就丢了 - 跨 exec 的方案只能是:让子进程自己重新
open + mmap同一文件,或改用memfd_create(Linux 3.17+)创建匿名内存文件并fd passing,但后者需要 Unix domain socket 配合 - 调试时用
cat /proc/PID/maps | grep yourfile确认映射是否还存在,比猜更可靠
最麻烦的从来不是 mmap 调用本身,而是谁负责创建文件、谁负责 truncate、谁负责清理、谁在什么时候看到哪部分已写入——这些边界不厘清,性能再高也崩在启动阶段。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











