1. mmap 是什么?
mmap(Memory Map)是操作系统提供的一种将文件或设备映射到进程虚拟地址空间的机制。
- 调用者通过
mmap()系统调用,把文件内容直接映射到一段虚拟内存地址区间。 - 之后对这段内存的读写,就等同于对文件的读写(内核负责同步)。
- 常见于 共享内存、零拷贝 I/O、内存型数据库。
2. mmap 的核心流程
假设我们调用:
c
复制编辑
void* addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
2.1 执行步骤
-
创建虚拟内存映射
- 内核在当前进程的虚拟地址空间分配一段连续地址(不一定物理连续)。
- 通过页表记录 这段虚拟内存 ↔ 文件物理页 的映射关系。
-
延迟加载(Lazy Loading)
-
映射成功后,内存内容还没真正加载。
-
当第一次访问某个映射页时,发生 缺页中断(Page Fault) :
- 内核从文件对应的磁盘块读入物理内存页。
- 更新页表,把虚拟地址映射到该物理页。
-
-
读写同步
- MAP_SHARED:对内存的修改会被标记“脏页”,OS 会在合适时机回写到文件(或显式调用
msync())。 - MAP_PRIVATE:写时复制(Copy-on-Write),修改只在当前进程生效,文件内容不变。
- MAP_SHARED:对内存的修改会被标记“脏页”,OS 会在合适时机回写到文件(或显式调用
3. mmap 的优势
3.1 减少数据拷贝
传统 read() 流程:
复制编辑
磁盘 → 内核缓冲区 → 用户缓冲区(2 次拷贝)
mmap 流程:
复制编辑
磁盘 → 内核页缓存(用户态直接访问该物理页,无需再次拷贝)
→ 避免了内核缓冲区到用户缓冲区的那次 copy,性能提升显著。
3.2 支持多进程共享
- 多个进程
mmap同一文件(MAP_SHARED) → 它们虚拟地址映射到同一物理页。 - 任何一个进程修改,其他进程立即可见(依赖 CPU cache coherency)。
3.3 灵活访问文件
- 直接用指针操作,不需要
read/write系统调用。 - 可随机访问任意偏移位置,效率更高。
4. mmap 在 Android 里的应用场景
-
MMKV(微信开源)
- 用
mmap把存储文件映射到内存,所有读写都是内存操作,落盘时调用msync()。 - 跨进程模式:多个进程映射同一文件,天然同步数据。
- 用
-
Binder
- Binder 驱动通过
mmap把 Binder 缓冲区映射到用户进程。 - 收发数据时直接写到对方映射区的物理页,实现一次拷贝。
- Binder 驱动通过
-
共享内存
- 进程间通信时,用
ashmem或memfd创建匿名共享内存,再mmap到双方进程。
- 进程间通信时,用
-
媒体/图像处理
MediaCodec、SurfaceFlinger等模块通过GraphicBuffer(本质是共享内存 + mmap)在进程间传输视频帧。
5. mmap 的风险 & 注意点
-
文件大小和内存占用
- 虚拟地址空间会被预留,虽然物理内存按需分配,但大文件映射会占虚拟内存上限。
-
同步问题
MAP_SHARED模式需要msync()或等待内核定时回写,才能保证数据立即落盘。- 异常退出可能丢失最近修改(同 SharedPreferences 的 apply 类似风险)。
-
信号安全
- 访问已经被
munmap()的地址会触发 SIGSEGV 崩溃。
- 访问已经被
-
跨架构兼容
- 32 位进程可用的虚拟地址空间更小(一般 <4GB),大文件映射要谨慎。
6. 面试高频问答
-
Q:mmap 为什么能一次拷贝?
- 因为它直接把文件物理页映射到用户虚拟空间,用户读写直接操作内核页缓存,省去了“内核 → 用户”的那次 copy。
-
Q:mmap 和普通 read/write 有什么区别?
read/write需要数据在内核缓冲区与用户缓冲区之间复制,mmap 直接映射,无需这步。read/write是显式调用系统调用,mmap 后是普通内存访问(隐式缺页中断)。
-
Q:跨进程共享是怎么做到的?
- 不同进程的虚拟地址都映射到同一物理页(共享文件/匿名内存),CPU 硬件保证缓存一致性。
-
Q:Android 的 MMKV 为什么选择 mmap?
- 因为它减少拷贝、支持多进程同步,性能高,而且内存数据更新后调用
msync落盘,可靠性可控。
- 因为它减少拷贝、支持多进程同步,性能高,而且内存数据更新后调用