在现代操作系统中,内存映射文件(mmap)是一种高效的文件访问机制。它允许程序将磁盘文件映射到进程的虚拟地址空间,从而使得文件内容像普通内存一样进行读写。相比传统的文件 I/O,mmap 在大文件处理、并行访问以及多进程共享数据方面有显著优势。
1. 为什么要使用 mmap?
-
零拷贝
传统的read/write需要在用户空间与内核空间之间进行数据拷贝。mmap 直接在虚拟内存里访问文件,省去了拷贝开销,尤其在处理 GB 级文件时效果更明显。 -
懒加载(Demand Paging)
文件内容仅在实际访问时才会被调入内存,减少了启动时的内存占用。 -
共享与同步
多个进程可以映射同一文件,且共享同一段物理内存,天然支持多进程并发访问。修改一个进程映射的内存会同步到文件,其他进程也能看到变化。 -
高性能随机访问
对文件中的任意位置进行读写,只需计算偏移并访问对应地址即可,无需移动文件指针。
2. 基本使用流程
下面给出一个完整的示例,演示如何在 C++ 中创建、映射、访问和同步一个文件。
#include <iostream>
#include <fstream>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
int main() {
const char* filePath = "demo.bin";
const size_t fileSize = 1024 * 1024; // 1 MB
// 1. 打开或创建文件
int fd = open(filePath, O_RDWR | O_CREAT, 0666);
if (fd == -1) {
std::perror("open");
return 1;
}
// 2. 确保文件大小
if (ftruncate(fd, fileSize) == -1) {
std::perror("ftruncate");
close(fd);
return 1;
}
// 3. 映射文件到内存
void* map = mmap(nullptr, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
std::perror("mmap");
close(fd);
return 1;
}
// 4. 在映射区写入数据
const char* msg = "Hello, mmap!";
std::memcpy(map, msg, std::strlen(msg) + 1);
// 5. 强制同步到磁盘(可选)
if (msync(map, fileSize, MS_SYNC) == -1) {
std::perror("msync");
}
// 6. 读取并打印
std::cout << "First bytes: " << static_cast<char*>(map) << std::endl;
// 7. 卸载映射
if (munmap(map, fileSize) == -1) {
std::perror("munmap");
}
close(fd);
return 0;
}
代码说明
| 步骤 | 说明 |
|---|---|
open |
以读写方式打开文件,必要时创建。 |
ftruncate |
确保文件有足够大小,否则 mmap 失败。 |
mmap |
将文件映射到进程地址空间。PROT_READ | PROT_WRITE 允许读写,MAP_SHARED 表示写入会同步到文件。 |
memcpy |
在映射内存中直接写入数据。 |
msync |
可选,强制把修改同步到磁盘,防止系统崩溃导致数据丢失。 |
munmap |
卸载映射,释放资源。 |
3. 注意事项
-
文件大小限制
mmap的长度参数受系统内存页大小限制。若映射大文件,需确保进程地址空间足够。 -
异常与错误
mmap返回MAP_FAILED时,需检查errno。常见错误包括ENOMEM(内存不足)、EACCES(权限不足)。 -
同步策略
MS_SYNC会阻塞直到同步完成;MS_ASYNC异步同步;MS_INVALIDATE让其他映射失效。根据业务需要选择。 -
多线程/多进程
对同一映射区的并发读写需使用互斥锁或原子操作,避免数据竞争。 -
内存泄漏
必须在程序结束前munmap所有映射区,否则会留下内存映射。
4. 性能对比
| 场景 | 传统 I/O | mmap |
|---|---|---|
| 读取大文件一次性 | read 循环 |
直接访问映射区 |
| 随机读取 | pread |
直接偏移访问 |
| 写入大量数据 | write 循环 |
memcpy + msync |
| 多进程共享 | 文件锁 | 共享映射 |
| 启动时占用 | 大 | 仅映射页 |
经验数据显示,在 1–10 GB 级别文件处理时,mmap 的 I/O 速度提升可达 3–5 倍。尤其在需要随机访问、并行处理时优势更为明显。
5. 进一步阅读
- 《Linux 程序设计》 – 章节 “内存映射文件”
- 官方文档:
man mmap - 文章:“Memory-Mapped I/O vs. Standard I/O: A Performance Study”(可在 ACM 等学术数据库获取)
通过掌握 mmap,你可以让 C++ 程序在文件处理上获得更高的效率和更简洁的代码结构。祝你编码愉快!