1. 内存映射文件
内存映射文件(Memory-Mapped File)是操作系统提供的一种高效文件访问机制,它允许进程将磁盘中的文件映射到自身虚拟地址空间中的一段区域。
建立映射后,程序无需通过 read/write 进行数据传输,而是通过普通内存访问触发操作系统的分页机制完成文件数据加载和写回。内存映射文件主要用于以下两个场景:
- 简化文件数据访问------使文件访问方式接近普通内存访问
- 实现多个进程之间的数据共享------多个进程可以通过映射同一个文件访问同一份数据
2. 传统文件访问方式的问题
在传统文件 I/O 模式下,程序需要通过系统调用主动完成文件数据的读取和写入,并自行管理缓冲区、文件偏移以及数据同步过程。
例如,访问一个文件通常需要经历以下步骤:
text
传统文件访问流程:
① open("文件.txt")
→ 打开文件,获取文件描述符
② seek(读写指针, 位置)
→ 定位文件读写位置
③ read(fd, buf, n)
→ 操作系统将文件数据从磁盘加载到内核空间,
再复制到用户空间缓冲区
④ 进程在用户空间处理数据
→ 对读取的数据进行计算或修改
⑤ write(fd, buf, n)
→ 将用户空间数据复制到内核空间,
再由操作系统写入磁盘
这种方式存在以下问题:
- 每次数据读写都需要显式调用
read和write - 程序需要自行管理数据缓冲区和文件偏移位置
- 数据在用户空间和内核空间之间需要额外复制
- 文件访问流程较为复杂,增加了程序开发负担
3. 内存映射文件的使用方式
内存映射文件通过 mmap 将文件内容映射到进程虚拟地址空间,使文件数据能够像普通内存数据一样被访问。
其基本访问流程如下:
css
内存映射文件访问流程:
① open("文件.txt")
→ 打开文件,获取文件描述符
② mmap(文件, 虚拟地址空间区域)
→ 建立文件与虚拟地址空间之间的映射关系,
返回映射区域的起始地址
③ 通过 指针 + 偏移量 访问数据
→ 像访问内存一样直接读写文件内容
④ munmap / close
→ 解除映射并关闭文件,
修改后的数据由操作系统负责同步到磁盘
4. 缺页异常与懒加载机制
调用 mmap 后,操作系统并不会立即将整个文件内容加载到物理内存中,而只是建立文件与虚拟地址空间之间的映射关系。
此时,映射区域对应的数据可能尚未存在于物理内存中,访问过程会触发缺页机制:
- 程序首次访问某个映射区域的数据。
- CPU 检测到对应虚拟地址没有有效的物理页映射。
- 触发缺页异常(Page Fault) 。
- 操作系统将对应文件数据加载到物理内存,并更新页表。
- 程序恢复执行,继续访问数据。
整个过程由操作系统自动完成,程序无需显式调用 read。
5. 代码示例对比
传统文件访问方式
scss
// 传统方式:手动管理缓冲区和系统调用
read(fd, buf, 1024);
buf[0] = 'A';
write(fd, buf, 1024);
在传统方式中,程序需要显式完成数据读取、修改以及写回操作。
内存映射方式
ini
// 内存映射方式:直接操作映射区域
p = mmap(NULL, 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
p[0] = 'A';
munmap(p, 1024);
通过 mmap 获取映射地址后,程序可以直接通过指针修改文件内容,数据同步由操作系统负责处理。
6. 核心优势对比
| 对比维度 | 传统文件 I/O(read/write) | 内存映射(mmap) |
|---|---|---|
| 数据流转 | 磁盘 → 内核空间 → 用户空间 | 磁盘 → 页缓存 → 进程映射区域 |
| 程序员负担 | 较高,需要管理缓冲区、偏移和读写时机 | 较低,获取映射地址后即可访问数据 |
| 系统调用开销 | 每次读写都需要进入内核态 | 主要发生在建立映射、缺页处理和数据同步阶段 |
| 数据同步方式 | 需要主动调用 write 写回 |
修改映射区域后由操作系统负责同步 |