3. mmap系统调用原理
mmap() 是 Linux 驱动与用户空间交互的核心系统调用,它允许用户程序将设备内存或文件直接映射到进程的虚拟地址空间。理解其原型、映射类型和标志是进行驱动开发和性能调优的基础。
3.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 | void * |
映射的起始虚拟地址建议值。通常设为 NULL,由内核选择合适地址。 |
| length | size_t |
映射的长度,以字节为单位。必须是系统页大小的整数倍(通常为 4096 字节)。 |
| prot | int |
映射区域的内存保护标志,决定读写执行权限。 |
| flags | int |
映射类型和属性标志,决定映射的可见性和行为。 |
| fd | int |
文件描述符。对于文件映射,指向被映射的文件;对于匿名映射,通常设为 -1。 |
| offset | off_t |
文件映射的起始偏移量,必须是页大小的整数倍。 |
返回值:成功时返回映射区域的起始虚拟地址(
void *);失败时返回 MAP_FAILED(即 (void *) -1),并设置 errno。
3.2 映射类型:文件映射与匿名映射
根据映射的后端存储来源,mmap() 主要分为两种类型:
3.2.1 文件映射
将磁盘文件或设备文件(如 /dev/mem、/dev/fb0)的一部分映射到进程地址空间。对映射区域的读写操作会直接反映到文件中(取决于 flags)。
3.2.2 匿名映射
不关联任何文件,由内核分配一块物理内存并映射到进程地址空间。通常用于进程间通信(共享内存)或分配大块内存。标志 MAP_ANONYMOUS 用于指定匿名映射,此时 fd 参数被忽略。
3.3 映射标志:MAP_SHARED 与 MAP_PRIVATE
flags 参数中的 MAP_SHARED 和 MAP_PRIVATE 决定了映射的可见性和写时复制行为,这是驱动开发中必须理解的核心概念。
| 标志 | 行为 | 写时复制 | 对其他进程可见 | 典型用途 |
|---|---|---|---|---|
| MAP_SHARED | 对映射区域的修改会直接写回底层文件或设备,并立即对其他映射同一区域的进程可见。 | 否 | 是 | 设备寄存器映射、共享内存、文件 I/O 优化 |
| MAP_PRIVATE | 对映射区域的修改不会写回文件,而是创建一份私有副本(写时复制)。 | 是 | 否 | 程序加载、数据段初始化、私有内存分配 |
驱动开发注意:在驱动中实现
mmap 操作时,必须根据设备特性正确设置 vma->vm_flags。对于需要共享硬件状态的设备(如帧缓冲、DMA 缓冲区),必须使用 MAP_SHARED;如果使用 MAP_PRIVATE,用户空间的写入将不会反映到硬件,导致驱动行为异常。
3.4 内存保护标志 (prot)
prot 参数指定映射区域的内存保护权限,可以组合使用:
- PROT_READ:页面可读。
- PROT_WRITE:页面可写。
- PROT_EXEC:页面可执行。
- PROT_NONE:页面不可访问。
性能提示:在驱动映射中,如果设备内存不需要执行权限,应避免设置
PROT_EXEC,以减少 TLB 缓存污染和潜在的安全风险。对于只读的硬件状态寄存器,仅设置 PROT_READ 即可。
3.5 mmap 映射流程示意图
以下 SVG 展示了从用户态调用 mmap() 到内核完成映射的完整流程:
3.6 驱动开发中的 mmap 实现要点
在 Linux 驱动中实现 mmap 操作时,通常需要完成以下步骤:
- 实现 file_operations 中的 mmap 回调:
int (*mmap)(struct file *filp, struct vm_area_struct *vma); - 设置 vma 操作:为
vma->vm_ops提供open、close、fault等回调。 - 建立物理地址到虚拟地址的映射:使用
remap_pfn_range()或io_remap_pfn_range()函数。 - 设置 vma 标志:根据设备特性设置
VM_SHARED、VM_IO、VM_PFNMAP等标志。
// 驱动 mmap 回调示例
static int my_device_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long pfn = my_device_get_phys_addr(filp);
// 设置映射标志:IO 内存、不可换出
vma->vm_flags |= VM_IO | VM_PFNMAP | VM_SHARED;
// 建立物理页帧到用户虚拟地址的映射
if (remap_pfn_range(vma, vma->vm_start, pfn,
vma->vm_end - vma->vm_start,
vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;
}
调试建议:使用
cat /proc/<pid>/maps 可以查看进程的映射信息,包括映射地址、权限、偏移量和设备号。这是排查 mmap 映射故障的第一手工具。