mmap
内存映射文件
mmap 系统调用把文件的一段区间关联到进程虚拟地址空间的映射区。它建立的是“虚拟地址区间 → 文件区间”的映射关系,并不会在调用时把整个文件区间立即读入主存。映射建立后,程序通过普通内存读写指令访问文件内容。
1 | int fd = open("data.bin", O_RDWR); |
这里的 p 是虚拟地址。程序访问 p[i] 时,MMU 查当前进程的页表;若该文件页尚未调入主存,就触发缺页故障,由操作系统把相应文件页读入页缓存并建立页表映射。
不同进程的虚拟地址空间和页表彼此独立。若它们用 MAP_SHARED 映射同一文件区间,各自的虚拟页可以映射到同一批页缓存物理页,从而共享文件内容;共享访问的先后顺序仍需由进程同步机制协调。
mmap 的关键是建立映射关系:
一次典型访问过程:
open打开文件,得到文件描述符。mmap在进程虚拟地址空间中划出一段区域。- OS 记录这段虚拟地址范围对应哪个文件、从哪个文件偏移开始、长度是多少、允许读写还是只读。
- 程序访问映射区中的某个地址。
- 若对应页尚未调入主存,产生缺页故障。
- OS 根据映射关系,从文件中读入对应页,放入页框。
- OS 更新页表项,重新执行原来的访存指令。
mmap 和请求分页是同一套机制的自然延伸:普通匿名页的后备存储可以是对换区,而内存映射文件页的后备存储是文件。
与 read/write 的区别
传统文件访问常见流程是:
1 | open -> seek -> read/write -> close |
read 把文件内容读到用户缓冲区,程序再访问缓冲区;write 把用户缓冲区内容写回文件。
mmap 的流程是:
1 | open -> mmap -> 直接访问映射区 -> msync/munmap |
| 对比项 | read/write | mmap |
|---|---|---|
| 访问方式 | 显式系统调用读写文件 | 用普通内存读写指令访问映射区 |
| 数据调入时机 | 调用 read 时读入 |
访问尚未在主存的映射页时调入 |
| 地址空间 | 用户自己提供缓冲区 | OS 分配或使用指定虚拟地址范围 |
| 适合场景 | 顺序读写、简单 I/O | 大文件随机访问、共享文件、减少显式拷贝 |
mmap 的优势是 I/O 被放进虚拟存储和缺页处理流程中。程序不用反复调用 read、write 移动文件指针和搬运缓冲区。
共享映射与私有映射
mmap 的映射方式决定写入行为。
| 类型 | 含义 | 写入结果 |
|---|---|---|
MAP_SHARED |
多个进程可映射同一个文件区域 | 修改对其他共享映射进程可见,并可写回文件 |
MAP_PRIVATE |
私有映射,采用写时复制COW | 写入只影响当前进程,不直接修改原文件 |
共享映射常用于:
- 多个进程共享同一个文件内容。
- 实现共享存储,进行进程间通信。
- 数据库、日志、索引文件等需要频繁随机访问的场景。
私有映射常用于:
- 加载可执行文件和共享库的只读代码段。
- 进程私有数据页的写时复制。
- 需要从文件初始化内容,但修改不影响原文件的场景。
MAP_PRIVATE 也是按需调页: 当进程第一次写某个共享物理页时,才复制出私有页。
多进程共享
多个进程可以把同一个文件映射到各自的虚拟地址空间。它们看到的虚拟地址可以不同,但页表指向同一批物理页框。
当进程 A 修改共享映射中的某个页时:
- CPU 写入进程 A 的虚拟地址。
- 页表把该虚拟页翻译到共享物理页。
- 该物理页被标记为脏页。
- 进程 B 若映射同一文件页,也会通过自己的虚拟地址看到这份修改后的物理页。
因此,共享映射能实现进程间共享数据。但共享不等于自动同步访问顺序;多个进程同时读写共享区域时,仍然需要锁、信号量、互斥量等同步机制。
写回与解除映射
映射区被写入后,对应物理页会变成脏页。脏页何时回到磁盘,取决于映射类型和同步方式。
| 操作 | 作用 |
|---|---|
msync |
请求把映射区的修改同步到文件 |
munmap |
解除虚拟地址区间与文件的映射 |
close(fd) |
关闭文件描述符,不等于取消已有映射 |
需要区分两个关系:
- 文件描述符用于建立映射;映射建立后,关闭
fd不会让映射区立刻失效。 - 映射区由
munmap解除;进程退出时,OS 也会回收映射。