mmap工程实践 用户态数据传输与驱动映射调试 - 第1节
1. mmap基础概念
mmap(memory map)是 Linux 内核提供的一种将文件或设备映射到进程虚拟地址空间的系统调用。映射完成后,进程可以像访问普通内存一样通过指针读写映射区域,内核在后台负责将内存操作同步到底层文件或设备。
1.1.1 系统调用原型
#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 |
flags |
映射类型与行为控制 | MAP_SHARED / MAP_PRIVATE / MAP_ANONYMOUS |
fd |
文件描述符(匿名映射时传 -1) | 有效 fd 或 -1 |
offset |
文件映射起始偏移量 | 必须是页大小对齐 |
关键点:mmap 返回的地址是虚拟地址,实际物理内存的分配和映射由内核在缺页异常时按需完成。映射区域的大小
length 会被内核向上对齐到页边界。
1.1.2 mmap 调用流程
核心理解:mmap 调用本身不分配物理内存,只创建虚拟内存区域(VMA)并初始化页表项。真正的物理页分配发生在首次访问映射地址时,由缺页异常(page fault)触发。
1.2 虚拟内存与物理内存映射关系
Linux 采用分页机制管理内存。每个进程拥有独立的 4GB(32位)或更大(64位)虚拟地址空间,通过多级页表将虚拟地址转换为物理地址。mmap 的核心工作就是建立并管理这种映射关系。
1.2.1 页表与映射层级
| 层级 | 名称 | 作用 |
|---|---|---|
| PGD | 页全局目录 | 顶级页表,每个进程一个 |
| P4D | 页四级目录 | 64位系统特有,扩展地址空间 |
| PUD | 页上级目录 | 三级页表,缩小页表项范围 |
| PMD | 页中间目录 | 二级页表,管理大块区域 |
| PTE | 页表项 | 直接映射虚拟页到物理页帧 |
1.2.2 缺页异常与物理内存分配
当进程首次访问 mmap 返回的虚拟地址时,CPU 发现页表项无效(未映射物理页),触发缺页异常。内核在缺页处理中完成物理页分配并建立映射:
- 文件映射缺页:从磁盘读取文件内容到分配的物理页,然后建立映射
- 匿名映射缺页:直接分配零填充的物理页,建立映射
- 写时复制(COW):私有映射在写操作时复制物理页
性能陷阱:mmap 映射大文件时,首次访问会产生大量缺页中断,导致明显的延迟。预读(readahead)和 MAP_POPULATE 标志可以提前触发缺页,将延迟分摊到映射阶段。
1.3 文件映射与匿名映射的区别
根据 flags 参数中是否包含 MAP_ANONYMOUS,mmap 分为文件映射和匿名映射两大类。两者在用途、数据来源和共享行为上有本质区别。
1.3.1 核心差异对比
| 对比维度 | 文件映射 | 匿名映射 |
|---|---|---|
| fd 参数 | 有效文件描述符 | -1 |
| 数据来源 | 磁盘文件内容 | 零填充内存(或未初始化) |
| 持久化 | MAP_SHARED 时写回文件 | 进程退出后数据丢失 |
| 共享范围 | 多进程通过同一文件共享 | 仅父子进程(fork 继承) |
| 典型用途 | 文件 I/O 加速、共享库加载 | 动态内存分配、进程间通信 |
| 缺页处理 | 从磁盘读取数据到物理页 | 分配零页(ZERO_PAGE)或新页 |
1.3.2 文件映射详解
文件映射将文件内容直接映射到进程地址空间。使用 MAP_SHARED 时,对映射内存的修改会写回文件;使用 MAP_PRIVATE 时,修改仅对当前进程可见,不会影响文件。
// 文件映射示例:将文件映射到内存并读取
int fd = open("/tmp/data.bin", O_RDWR);
void *map = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
// 直接通过指针读写文件内容
memcpy(map, "hello", 5);
// 修改自动同步到文件
munmap(map, 4096);
close(fd);
1.3.3 匿名映射详解
匿名映射不关联任何文件,用于分配大块内存或实现父子进程间的共享内存。内核自动分配零填充的物理页,进程退出时自动释放。
// 匿名映射示例:分配 1MB 匿名内存
size_t size = 1024 * 1024; // 1MB
void *buf = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
// 使用匿名内存
memset(buf, 0, size);
// 释放
munmap(buf, size);
1.3.4 选择指南
- 需要持久化数据:使用文件映射 + MAP_SHARED,数据自动写回文件
- 进程间共享内存:文件映射(通过同一文件)或匿名映射(仅 fork 后)
- 大块动态内存:匿名映射比 malloc 更高效,且可指定大页(HugeTLB)
- 加载共享库:内核自动使用文件映射 + MAP_PRIVATE 加载 .so 文件
- 驱动数据传输:设备驱动通常使用文件映射(通过设备文件 fd)将内核缓冲区映射到用户态