Linux 内存映射(Memory Mapping)是一种将文件或其他资源直接映射到进程虚拟内存地址空间的机制,允许进程像访问内存一样访问文件或设备。这种机制通过 mmap()
系统调用实现,常用于高效文件操作、进程间共享内存等场景。
1. 内存映射的核心概念
-
虚拟内存区域:每个进程的虚拟地址空间中存在一个“内存映射区域”,用于存放映射的文件或匿名内存。(用户空间分为代码段、数据段、堆、栈,还有内存映射区域。)
-
页缓存(Page Cache):文件被映射到内存时,实际数据存储在页缓存中,内核自动管理缓存的加载和回写。
-
共享与私有映射:
- 共享映射(MAP_SHARED):修改会同步到文件,其他进程可见。
- 私有映射(MAP_PRIVATE):修改仅对当前进程有效,不会写回文件。
-
具体流程:
- mmap:进程通过调用 mmap 系统调用来请求将文件或设备映射到进程的虚拟地址空间。
- 更新页表:操作系统更新进程的页表,以便将虚拟地址映射到正确的物理内存地址。
- 虚拟地址(vm_addr):CPU生成的虚拟地址,用于访问进程的虚拟内存空间。
- MMU查询:当CPU需要访问虚拟地址时,它会向内存管理单元(MMU)查询该虚拟地址对应的物理地址。
- 查进程用户空间页表:MMU会查询进程的用户空间页表,以找到与虚拟地址对应的物理地址。
- 物理地址:MMU返回虚拟地址对应的物理地址。
- 直接操作物理内存的数据:一旦获得了物理地址,CPU就可以直接访问物理内存中的数据。
2. mmap() 系统调用
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
参数说明:
- addr:建议映射的起始地址(通常设为
NULL
,由内核选择)。 - length:映射区域的长度。
- prot:保护权限(
PROT_READ
、PROT_WRITE
、PROT_EXEC
、PROT_NONE
)。 - flags:映射类型(
MAP_SHARED
、MAP_PRIVATE
、MAP_ANONYMOUS
等)。 - fd:文件描述符(匿名映射时设为
-1
)。 - offset:文件偏移量(通常为 0)。
返回值:
- 成功:返回映射区域的起始地址。
- 失败:返回
MAP_FAILED
((void *) -1
)。
3. 内存映射的类型
(1)文件映射
将文件内容映射到内存,适用于高效读写文件:
int fd = open("file.txt", O_RDWR);
void *addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
(2)匿名映射
不关联文件,用于动态分配内存或进程间共享:
void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
4. 内存映射的优势
- 减少数据拷贝:文件直接通过页缓存访问,无需
read()/write()
的系统调用开销。 - 高效处理大文件:仅加载实际访问的部分到内存,避免一次性读取整个文件。
- 共享内存:多个进程映射同一文件/匿名区域,可实现高效进程间通信(IPC)。
- 延迟加载:数据按需加载到内存(缺页中断机制)。
5. 典型应用场景
- 文件读写优化:数据库(如 LevelDB)、图像处理库(如 OpenCV)常用
mmap
加速 I/O。 - 动态库加载:加载共享库(
.so
文件)时,系统通过内存映射将代码段映射到进程空间。 - 进程间通信(IPC):共享内存(搭配
MAP_SHARED
)。 - 内存分配:
glibc
的malloc()
对大块内存使用mmap
分配。
6. 注意事项
- 对齐要求:
offset
必须是页大小的整数倍(通常 4KB)。 - 权限匹配:
prot
和flags
必须与文件打开模式兼容(如只读文件不能映射为PROT_WRITE
)。 - 同步数据:修改后需调用
msync()
确保数据写回磁盘。 - 资源释放:
munmap()
不会自动关闭文件描述符。 - 信号处理:访问超出文件大小的映射区域可能触发
SIGBUS
信号。
7. 示例代码
通过 mmap 修改文件内容
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("test.txt", O_RDWR);
struct stat st;
fstat(fd, &st);
off_t size = st.st_size;
// 映射文件到内存
char *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
return -1;
}
// 修改文件内容
addr[0] = 'H';
addr[1] = 'i';
munmap(addr, size); // 解除映射
close(fd);
return 0;
}
8. 相关工具
- /proc/[pid]/maps:查看进程的内存映射区域。
- pmap:命令行工具,显示进程内存映射信息。
总结
Linux 内存映射通过 mmap
提供了一种高效、灵活的内存管理机制,广泛应用于文件操作、进程间通信和内存分配。理解其工作原理和适用场景,能帮助开发者优化程序性能并简化复杂任务(如共享内存)。