mmap比read/write更适合超大文件,因其将文件映射为虚拟内存页,避免内核态与用户态间数据拷贝,降低系统调用开销,借助page cache和预读提升随机读写性能。

为什么 mmap 比 read/write 更适合超大文件
因为 mmap 把文件直接映射成进程的虚拟内存页,省去了内核态和用户态之间反复拷贝数据的过程。对 GB 级文件做随机读写时,mmap 的延迟更稳定,且能借助操作系统的 page cache 和预读机制自动优化 I/O。
但注意:它不等于“零拷贝”——页错误触发时仍要从磁盘加载数据;真正节省的是 read/write 系统调用开销和内存拷贝。
- 适用场景:需要频繁跳转读取(如解析日志中的某几列)、只读分析、或配合
msync做低频持久化写入 - 不适用场景:小文件(read + 大缓冲区更简单)、或必须控制每次 I/O 大小的嵌入式环境
- 性能影响:映射本身很快,但首次访问未缓存页会阻塞;建议用
madvise(fd, MADV_WILLNEED)提示内核预加载
如何正确调用 mmap 处理超大文件
mmap 不是万能的,参数设错会导致 ENOMEM、EINVAL 或静默失败。关键在 length、offset 和 flags 的组合。
-
length必须是页对齐的(通常 4KB),否则系统会向上取整;可先用sysconf(_SC_PAGESIZE)获取 -
offset必须是页对齐的,否则mmap直接返回MAP_FAILED - 读写映射推荐用
PROT_READ | PROT_WRITE+MAP_PRIVATE(写时不落盘)或MAP_SHARED(写时同步到文件) - 64 位系统上,不要用
int存文件大小——off_t和size_t才可靠
示例片段:
立即学习“C++免费学习笔记(深入)”;
int fd = open("huge.bin", O_RDONLY);
off_t file_size = lseek(fd, 0, SEEK_END);
size_t page_size = sysconf(_SC_PAGESIZE);
size_t map_len = ((file_size + page_size - 1) / page_size) * page_size;
void* addr = mmap(nullptr, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) { /* handle error */ }
// 使用 addr 作为 char* 访问数据
常见崩溃点:SIGBUS 和指针越界
SIGBUS 是 mmap 最典型的运行时错误,不是段错误(SEGFAULT),常被误判为代码 bug。它本质是访问了尚未加载或已失效的内存页。
- 最常见原因:访问位置超出
mmap的length,哪怕只越界 1 字节 - 另一个原因:文件被外部截断(
truncate),导致映射区域部分无效 - 还有一种:映射了
MAP_PRIVATE区域后,又用munmap释放了中间一段,再访问该段 - 调试技巧:用
strace -e trace=mmap,munmap,open,close看映射范围是否匹配实际访问偏移
munmap 后还能不能访问?以及 msync 的坑
调用 munmap 后,对应地址空间立刻失效,再次访问必然触发 SIGSEGV 或 SIGBUS,不存在“还能用一会儿”的侥幸。
msync 的行为则更微妙:
- 只对
MAP_SHARED映射有意义;MAP_PRIVATE下调用它没效果 -
MS_SYNC是阻塞写盘,耗时可能很长(尤其机械盘);MS_ASYNC只是提交写请求,不等完成 - 即使成功返回
msync,也不能保证磁盘已落盘——除非文件系统挂载时加了sync或用了O_SYNC打开 fd - 如果只改了部分页,传给
msync的addr和length必须精确覆盖修改范围,否则可能漏刷
真正难处理的是:映射期间文件被其他进程修改。mmap 不提供版本保护,读到脏数据或部分更新内容都可能发生——这得靠应用层加锁或校验,mmap 本身不管。










