1. mmap 基础概念:内存映射、传统读写与驱动通信

内存映射(Memory Mapping)是 Linux 内核提供的一种将文件或设备内容直接映射到进程虚拟地址空间的机制。通过 mmap() 系统调用,用户空间程序可以像访问普通内存一样访问文件内容或设备内存,而无需显式调用 read() / write()。在驱动开发中,mmap 是实现用户空间与内核空间高效共享内存的核心手段。

1.1 什么是内存映射

内存映射的本质是在进程的虚拟地址空间中创建一段映射区域,该区域与某个文件描述符(fd)关联。内核负责将虚拟地址与物理内存页(或文件缓存页)建立映射关系。当进程访问映射区域时,触发缺页异常,内核自动将对应页加载到物理内存。

核心要点: 映射建立后,读写操作直接作用于物理内存页,无需系统调用和数据拷贝。
内存映射核心概念图 用户空间 (User Space) 进程 A 虚拟地址 0x7f.. 映射区域 进程 B 虚拟地址 0x7e.. 映射区域 内核空间 (Kernel Space) 物理内存页 (共享物理帧) 文件/设备 (磁盘/硬件) 多个进程可映射同一物理页 实现共享内存通信

1.2 mmap 与传统读写文件的区别

传统文件 I/O(read() / write())需要经过用户空间缓冲区 ↔ 内核缓冲区 ↔ 磁盘/设备的多次数据拷贝。mmap 则通过直接映射省去中间拷贝,性能显著提升。

对比维度 传统 read/write mmap 内存映射
数据拷贝次数 至少 2 次(内核↔用户) 0 次(缺页时 1 次加载)
系统调用开销 每次读写都需系统调用 映射后无系统调用
缓存利用 依赖用户缓冲区 直接使用内核页缓存
适合场景 小文件、流式读写 大文件、随机访问、共享内存
编程复杂度 简单直观 需处理映射大小、同步
性能关键: 对于大文件(> 1 MB)或频繁随机访问,mmap 通常比 read/write 快 2~5 倍,因为减少了上下文切换和数据拷贝。
传统 I/O 与 mmap 数据流对比 传统 read/write 用户缓冲区 内核缓冲区 磁盘/设备 拷贝 ① 拷贝 ② 每次读写:2 次拷贝 + 系统调用 mmap 内存映射 用户映射区 物理内存页 磁盘/设备 缺页加载 按需加载 映射后:0 拷贝,无系统调用

1.3 mmap 在驱动与用户空间通信中的角色

在 Linux 设备驱动中,mmap 是实现用户空间与内核空间高效数据交换的核心机制之一。驱动通过实现 file_operations 中的 mmap 方法,将内核内存(如 DMA 缓冲区、硬件寄存器、共享内存池)直接映射到用户进程的虚拟地址空间。

驱动 mmap 通信的典型流程

  1. 驱动分配内核内存:使用 kmalloc()、dma_alloc_coherent() 或 alloc_pages() 分配物理连续或非连续内存。
  2. 驱动实现 mmap 回调:在 file_operations.mmap 中调用 remap_pfn_range() 或 dma_mmap_coherent() 建立映射。
  3. 用户空间调用 mmap():通过 mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) 获取映射地址。
  4. 直接访问共享内存:用户程序通过返回的指针直接读写,驱动通过内核指针访问同一物理内存。
  5. 同步机制:使用原子操作、内存屏障或 msync() 保证数据一致性。
常见陷阱: 驱动 mmap 必须确保映射的内存页在进程生命周期内有效,不能释放正在被映射的页面。同时要注意缓存一致性(Cache Coherency),尤其在 DMA 场景下。
驱动与用户空间 mmap 通信架构 用户空间 应用程序 mmap() → 返回指针 ptr 共享内存访问 ptr[0] = data; // 直接写 val = ptr[1]; // 直接读 系统调用接口 (VFS) open / mmap / ioctl 内核空间 设备驱动 file_operations.mmap remap_pfn_range() dma_mmap_coherent() 内核内存缓冲区 kmalloc / DMA 池 物理地址: 0x1000_0000 硬件设备 DMA / 寄存器 / FIFO 系统调用 共享物理内存

1.4 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) NULL(内核选择)
length 映射长度(字节),必须是页大小整数倍 4096, 65536
prot 内存保护标志 PROT_READ | PROT_WRITE
flags 映射类型与行为 MAP_SHARED 或 MAP_PRIVATE
fd 文件描述符(设备或文件) open() 返回值
offset 文件内偏移,必须是页大小整数倍 0
驱动通信关键: 使用 MAP_SHARED 标志,所有对映射区域的修改会直接写回内核内存,驱动和用户空间看到的是同一份数据。MAP_PRIVATE 会触发写时拷贝(COW),不适合共享内存。

1.5 本章小结

  • 内存映射 将文件或设备内存直接映射到进程虚拟地址空间,通过缺页机制按需加载。
  • 与传统 I/O 相比,mmap 省去数据拷贝和系统调用,适合大文件随机访问和共享内存。
  • 在驱动通信中,mmap 是零拷贝共享内存的核心,驱动通过 remap_pfn_range() 将内核内存暴露给用户空间。
  • 关键参数:MAP_SHARED 保证修改可见性,length 和 offset 必须页对齐。