mmap读大文件比fread快,因其绕过标准I/O缓存层,将文件页直接映射至进程虚拟地址空间,实现零拷贝内存访问,避免反复系统调用与态切换;但需正确配置MAP_PRIVATE|MAP_POPULATE、检查返回值、确保长度≤文件大小并调用munmap。

为什么 mmap 读大文件比 fread 更快
mmap 不走标准 I/O 缓存层,直接把文件页映射进进程虚拟地址空间,后续读取就是普通内存访问。对 GB 级文件,避免反复 fread 的系统调用开销和用户态/内核态切换,尤其适合随机读或多次扫描场景。
但注意:mmap 不等于“立刻加载全部数据”——它只是建立映射,实际页在首次访问时才触发缺页中断并从磁盘加载(lazy loading)。这对启动快、内存占用低有帮助,但也意味着第一次访问某块区域会有延迟。
如何正确调用 mmap 处理只读大文件
关键不是“怎么映”,而是“怎么配参数 + 怎么收尾”。常见错误是忽略 MAP_PRIVATE 和 MAP_POPULATE 的取舍,或忘记 munmap。
-
fd必须是已打开的只读文件描述符(open(path, O_RDONLY)),且文件大小不能为 0(否则mmap返回MAP_FAILED) - 推荐用
MAP_PRIVATE | MAP_POPULATE:前者避免写时复制干扰,后者让内核预加载所有页(减少后续缺页中断,适合顺序读大文件) -
length必须 ≤ 文件大小;若传入 > 文件大小,mmap可能成功,但越界访问会触发SIGBUS - 映射后务必检查返回值是否为
MAP_FAILED,并用errno判断原因(如EINVAL常因文件未对齐或长度非法)
示例片段:
立即学习“C++免费学习笔记(深入)”;
int fd = open("data.bin", O_RDONLY);
struct stat st;
fstat(fd, &st);
void *addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
close(fd);
return;
}
// 使用 addr 指针读取...
munmap(addr, st.st_size);
close(fd);
读取过程中遇到 SIGBUS 怎么定位
SIGBUS 几乎都源于访问了未映射/无效的地址,不是程序逻辑错,而是 mmap 配置或使用不当。
- 最常见:文件被其他进程截断(
truncate或rm + recreate),导致映射区域超出当前文件长度 - 误用
MAP_SHARED后又调用了ftruncate,引发不一致 -
length传得比lseek当前文件大小还大,而文件本身没填满(比如空洞文件) - 64 位程序在 32 位系统上编译,地址空间不足(少见但可能)
调试建议:用 strace -e trace=mmap,munmap,ftruncate 观察系统调用序列;或加 sigaction(SIGBUS, ...) 捕获信号并打印 si_addr,确认出问题的虚拟地址是否落在预期映射范围内。
比 mmap 更稳的替代方案有哪些
不是所有场景都适合 mmap。当文件极不稳定(频繁被外部修改)、内存受限(映射后 RSS 突增)、或需要精确控制缓存行为时,应考虑其他方式。
- 分块
read()+posix_fadvise(fd, offset, len, POSIX_FADV_DONTNEED):手动控制预读与释放,更可控,兼容性更好 -
std::ifstream配合rdbuf()->pubsetbuf()设置大缓冲区(但仍是流式,不适合随机跳转) - Linux 专属:
io_uring提交异步读请求,零拷贝 + 批量处理,适合高并发读多个大文件
mmap 的真正优势在于“一次映射,无限次指针访问”,一旦映射成功,后续读就是纯 CPU 操作。但它的脆弱点也很明确:依赖文件存在性、大小稳定性、以及内核页表管理策略。用之前先问一句:这个文件,在整个读取周期里,真的不会被别的进程碰吗?


















