Linux下mmap文件需三步:open、ftruncate确保文件大小、mmap映射;注意MAP_SHARED回写、msync强持久化、fd须支持映射、错误检查依赖errno。

Linux 下用 mmap 读写文件,核心就三步:打开 + 映射 + 访问
Windows 没有 mmap,那是 CreateFileMapping 和 MapViewOfFile 的事;C++ 标准库也不提供跨平台内存映射接口。所以谈 mmap,默认你跑在 Linux/Unix 类系统上,且用的是 POSIX 接口。
常见错误是直接对普通文件调用 mmap 却忘了先 ftruncate —— 映射区域不能超出文件当前大小,否则访问时会触发 SIGBUS(不是 SEGFAULT,这点容易误判)。
-
open()要带O_RDWR(写必须),如果只读用O_RDONLY,但后续不能改写 -
ftruncate(fd, size)必须在mmap前调用,确保文件有足够空间——哪怕你只想读,也要保证映射长度 ≤ 文件大小 -
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0):注意MAP_SHARED才能回写到文件;MAP_PRIVATE改动只在内存,不落盘
mmap 返回 MAP_FAILED 的典型原因
不是所有失败都报 errno = ENOMEM,很多是隐性条件不满足。比如:size 为 0、fd 已关闭、文件系统不支持(如某些 NFS 或 /proc 下的伪文件)、或映射长度超过 RLIMIT_AS 限制。
最容易被忽略的是:文件描述符 fd 必须指向一个**支持 mmap 的文件类型**。普通磁盘文件可以,但管道(pipe)、终端(/dev/tty)、socket、甚至某些 tmpfs 挂载点(取决于内核配置)可能拒绝映射,此时 mmap 直接返回 MAP_FAILED,errno 是 ENODEV 或 EINVAL。
- 检查
errno比检查返回值更关键:用if (addr == MAP_FAILED) { perror("mmap"); } - 映射前用
lseek(fd, 0, SEEK_END)+lseek(fd, 0, SEEK_SET)确认文件可定位,排除只读挂载或权限问题 - 不要假设
size可以任意传——内核要求页对齐,实际会向上按页(通常 4KB)取整,但传入非对齐值本身不报错
写完记得 msync 吗?大多数时候不用,但有些场景必须
MAP_SHARED 下,写内存一般会异步刷盘,类似 write() 后依赖内核回写。如果你需要**强持久化保证**(比如关键日志、数据库 WAL),就得调 msync(addr, size, MS_SYNC) —— 否则断电可能丢数据。
但别滥用:MS_SYNC 是阻塞的,性能开销大;MS_ASYNC 只是“建议内核尽快刷”,不保证顺序或完成时间。多数应用只需依赖内核默认行为,除非你在实现存储引擎或做崩溃一致性校验。
-
msync前不必munmap,但munmap不触发刷盘 - 映射区域被
fork()后,子进程继承映射,但父子写同一地址是否冲突,取决于MAP_SHARED还是MAP_PRIVATE - 多线程同时写同一映射区域,需自行加锁——
mmap不提供原子性
C++ 封装 mmap 时,RAII 管理比想象中难
表面看,用 unique_ptr + 自定义 deleter 就能自动 munmap,但漏掉两个关键点:一是 fd 本身也要关,二是 msync 时机不可控。强行塞进析构函数,会导致每次销毁都刷盘,性能崩坏。
更现实的做法是:把映射封装成类,但 msync 暴露为显式方法;fd 和 addr 分开管理生命周期;或者干脆不封装,只在局部作用域里直写三行原生调用——简单逻辑下,少一层抽象反而更稳。
- 自定义 deleter 里调
munmap是对的,但别在里面调close(fd)——fd可能被其他地方复用 -
mmap成功后,addr是void*,C++ 中转成具体类型指针要小心对齐和别名规则(比如用reinterpret_cast读结构体,确保该结构体无 padding 且按需packed) - 调试时用
/proc/PID/maps查看映射是否生效、权限位是否正确(rw还是r-)
内存映射不是银弹,小文件用 read/write 更轻量,频繁随机访问才显优势;而真正难的从来不是调通 mmap,是怎么在崩溃恢复、并发修改、跨进程同步这些边界上不翻车。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











