1. mmap 基础概念
mmap(memory map)是 Linux 系统提供的一种高效内存映射机制,它允许用户空间程序直接访问内核空间或设备内存,而无需通过传统的 read/write 系统调用进行数据拷贝。在设备驱动开发中,mmap 是实现高性能数据交互的核心手段之一。
1.1 mmap 系统调用原理
mmap 系统调用的原型定义在 <sys/mman.h> 中:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
参数说明如下表:
| 参数 | 含义 | 典型值 |
|---|---|---|
addr |
建议的映射起始地址(通常传 NULL 由内核选择) | NULL |
length |
映射区域的字节长度 | 页对齐大小(如 4096) |
prot |
映射区域的访问权限 | PROT_READ | PROT_WRITE |
flags |
映射类型和行为标志 | MAP_SHARED 或 MAP_PRIVATE |
fd |
文件描述符(设备文件或普通文件) | open() 返回的 fd |
offset |
文件内偏移量(必须页对齐) | 0 |
关键机制: mmap 的核心在于缺页中断。调用 mmap 时,内核仅在进程的虚拟地址空间中建立 VMA(虚拟内存区域),并不立即分配物理页面。只有当进程实际访问映射区域时,才会触发缺页异常,由内核的缺页处理程序完成物理页面的分配和映射。
1.2 虚拟内存与物理内存映射关系
现代操作系统采用虚拟内存管理,每个进程拥有独立的 4GB(32位)或更大(64位)虚拟地址空间。mmap 的本质就是在虚拟地址和物理地址之间建立页表映射。
映射过程中涉及的核心数据结构:
- task_struct:进程描述符,包含
mm_struct指针 - mm_struct:内存描述符,管理整个进程地址空间
- vm_area_struct (VMA):描述一段连续的虚拟地址空间,包含起始地址、结束地址、权限、文件映射信息等
- 页表 (Page Table):由 MMU 硬件遍历,完成虚拟地址到物理地址的转换
1.3 mmap 在驱动中的作用
在设备驱动开发中,mmap 提供了用户态与内核态之间零拷贝的数据交换通道。传统 read/write 方式需要在内核缓冲区和用户缓冲区之间进行数据拷贝,而 mmap 允许用户程序直接访问内核管理的物理内存。
驱动中使用 mmap 的典型场景:
| 应用场景 | 传统方式 | mmap 方式 | 性能提升 |
|---|---|---|---|
| 视频帧采集 | read() 每次拷贝一帧 | 直接映射 DMA 缓冲区 | 消除拷贝,帧率提升 2~5 倍 |
| GPU 显存访问 | ioctl + 数据搬运 | 映射显存到用户空间 | 延迟降低 10 倍以上 |
| FPGA 共享内存 | 驱动中转数据 | 用户直接读写 FPGA 内存 | 带宽接近硬件极限 |
| 大容量数据采集 | 多次 read 系统调用 | 一次 mmap + 指针操作 | 系统调用开销降为 0 |
驱动开发者视角: 实现 mmap 的关键在于驱动中提供
file_operations 结构体的 .mmap 回调函数。该回调需要调用 remap_pfn_range() 或 io_remap_pfn_range() 内核 API,将物理页帧号(PFN)映射到用户空间的 VMA 中。
1.4 mmap 与常规 I/O 的对比
理解 mmap 的优势需要对比传统 I/O 路径:
- 传统 read/write:用户缓冲区 → 系统调用 → 内核缓冲区 → 设备 → 内核缓冲区 → 系统调用返回 → 用户缓冲区(至少 2 次数据拷贝)
- mmap 方式:用户空间直接映射设备内存 → 缺页时建立页表 → 用户直接读写(0 次数据拷贝)
注意事项: mmap 虽然高效,但并非万能。映射大块物理内存时需考虑页表开销;映射设备寄存器时需注意缓存一致性(使用
pgprot_noncached() 设置页属性);多进程共享映射时需处理同步问题。