15、内存映射mmap:文件映射与匿名映射、大块内存分配
说到动态内存管理,大家第一反应肯定是 malloc、free 这些老朋友。但说实话,当你需要分配超大块内存,或者想在进程间共享数据时,这些常规手段就不太够用了。这时候,mmap 就该登场了。
我个人习惯把 mmap 看作是操作系统给我们的一把“瑞士军刀”。它既能处理文件,又能分配内存,还能搞共享。今天我们就把它掰开揉碎了讲清楚。
15.1 什么是mmap?
mmap 的全称是 memory map,也就是内存映射。它的核心思想很简单:把文件或者设备的一部分,直接映射到进程的虚拟地址空间里。这样一来,你读写内存就像在读写文件,反之亦然。
你想想看,这省掉了多少中间环节?不用 read、write 系统调用,不用用户态和内核态之间的数据拷贝。说白了,就是让文件操作变得跟内存操作一样快。
核心概念:mmap 创建了一个映射关系,把文件内容“贴”到进程的虚拟内存地址上。之后对这块内存的读写,操作系统会自动同步回文件。
15.2 两种映射模式
根据映射的对象不同,mmap 分为两种模式。我在项目中都遇到过,各有各的妙用。
15.2.1 文件映射
文件映射就是把一个打开的文件映射到内存里。最常见的场景就是大文件读写。我记得有一次处理一个 4GB 的日志文件,用 fread 读到内存里直接 OOM 了。换成 mmap 后,问题迎刃而解。
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("large_file.dat", O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
// 获取文件大小
off_t size = lseek(fd, 0, SEEK_END);
// 映射整个文件
char *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 现在可以直接读写 addr 了
printf("文件第一个字节: %c\n", addr[0]);
addr[0] = 'A'; // 修改会同步回文件
// 解除映射
munmap(addr, size);
close(fd);
return 0;
}
小提示:文件映射的 MAP_SHARED 标志表示对内存的修改会写回文件。如果你只想读不想写,用 MAP_PRIVATE,修改只在当前进程可见。
15.2.2 匿名映射
匿名映射不关联任何文件,纯粹用来分配内存。它跟 malloc 的区别在于:mmap 分配的内存是页对齐的,而且可以指定权限。我曾经用它来实现一个共享内存的缓存系统,效果非常好。
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
int main() {
// 分配 1MB 匿名内存
size_t size = 1024 * 1024;
char *buf = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (buf == MAP_FAILED) {
perror("mmap");
return 1;
}
// 使用这块内存
strcpy(buf, "Hello from anonymous mmap!");
printf("%s\n", buf);
// 释放
munmap(buf, size);
return 0;
}
注意:匿名映射的 fd 参数必须传 -1,offset 传 0。这是固定写法,别搞错了。
15.3 大块内存分配
为什么说 mmap 适合大块内存分配?这得从 malloc 的实现说起。当 malloc 分配超过 128KB 的内存时,它底层其实也是调用 mmap 来完成的。嗯,这里要注意:malloc 是个通用接口,而 mmap 是它的“幕后英雄”。
直接使用 mmap 分配大块内存有几个好处:
- 页对齐:分配的内存起始地址一定是页边界(通常是 4KB),这对某些硬件操作很友好。
- 独立释放:用
mmap分配的内存可以单独用munmap释放,不会影响堆里的其他内存块。 - 权限控制:你可以指定只读、读写、可执行等权限,比
malloc灵活得多。
我曾经在做一个视频处理项目时,需要分配几百 MB 的帧缓冲区。用 malloc 分配这么大的连续内存,有时候会失败。改用 mmap 后,再也没出过问题。
15.4 知识体系图
下面这张图帮你理清 mmap 的核心脉络:
15.5 使用建议与避坑
最后,分享一些我在实际项目中积累的经验:
- 不要频繁调用 mmap/munmap:每次映射和解除映射都是系统调用,开销不小。如果频繁分配小块内存,还是用
malloc更合适。 - 注意文件大小变化:如果映射后文件被截断,访问超出部分会触发 SIGBUS 信号。我曾经因为这个 bug 排查了一整天。
- 多进程共享要加同步:用
MAP_SHARED做进程间通信时,记得用互斥锁或信号量保护共享数据。 - 32位系统有地址空间限制:在 32 位系统上,进程虚拟地址空间只有 4GB,映射大文件时可能不够用。64 位系统基本不用担心。
一句话总结:mmap 是 C 语言动态内存管理的“重武器”,适合大块内存分配和文件映射场景。日常开发中,小内存用 malloc,大内存用 mmap,各司其职。
公众号:蓝海资料掘金营,微信deep3321