学习笔记:Linux 驱动开发之mmap与内存映射
1. 核心概念:什么是mmap?
mmap(Memory Map) 是一种内存映射文件的方法。在嵌入式 Linux 驱动开发中,它主要用于将外设的物理地址(如 GPIO 寄存器)映射到用户进程的虚拟地址空间。
- 传统方式:用户态
read/write<–> 内核态拷贝数据 <–> 驱动操作寄存器。 - mmap 方式:用户态指针 <–> MMU 直接转换 <–> 硬件寄存器。(零拷贝,速度极快)
2. 核心难点:用户虚拟地址 vs 内核虚拟地址
这是理解 Linux 内存管理的关键分水岭。
2.1 对比图表
| 特性 | 用户虚拟地址 (UVA) | 内核虚拟地址 (KVA) |
|---|---|---|
| 定义 | 应用程序看到的地址。 | 操作系统内核驱动看到的地址。 |
| 地址范围 (32位) | 0x00000000~0xBFFFFFFF(0~3G) | 0xC0000000~0xFFFFFFFF(3G~4G) |
| 可见性 | 私有。进程A和进程B的0x1000互不相干。 | 全局共享。所有进程进入内核态后看到的都是同一份。 |
| 映射工具 | mmap() | ioremap() |
| 访问权限 | 仅当前进程有效,进程结束即销毁。 | 系统启动即存在,只有内核代码可读写。 |
| 物理对应 | 通常不连续,按需分配(缺页中断)。 | 逻辑地址线性映射,vmalloc/ioremap非线性。 |
2.2 它们与物理地址的关系
假设 i.MX6ULL 的一个 LED 寄存器物理地址是0x0209C000:
- CPU/MMU:只认物理地址
0x0209C000。 - 驱动程序 (KVA):通过
ioremap拿到一个地址(如0xF0001000)。驱动写0xF0001000-> MMU 查表 -> 写入物理地址。 - 应用程序 (UVA):通过
mmap拿到一个地址(如0xB7001000)。应用写0xB7001000-> MMU 查表 -> 写入物理地址。
结论:UVA 和 KVA 就像是通往同一个房间(物理地址)的两扇不同的门。一扇门开在“用户公寓”(0-3G),另一扇门开在“管理员办公室”(3-4G)。
3.mmap的实现流程(驱动与应用配合)
3.1 应用程序做了什么?
应用程序中的mmap最终会调用驱动中实现的.mmap接口(即下面的my_driver_mmap函数)
int fd = open("/dev/my_led", O_RDWR); // 申请映射:从 offset 0 开始,映射 4096 字节 unsigned char *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 直接操作硬件! *addr = 0x01; // 点灯3.2 驱动程序做了什么? (fops.mmap)
驱动的核心任务是构建页表。
- 获取物理地址:知道你要操作哪个寄存器(例如
0x0209C000)。 - 计算页帧号 (PFN):Linux 内存管理以“页”为单位(通常 4KB)。
PFN = 物理地址 >> PAGE_SHIFT(即除以 4096)。
- 调用
remap_pfn_range:这是核心函数。它负责修改当前进程的页表,建立 UVA 到 物理地址 的映射。
// 驱动代码示例 static int my_driver_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long phy_addr = 0x0209C000; // 硬件物理地址 // 关键:设置为不使用缓存 (Nocache)!否则寄存器读写会出错 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); // 建立映射:把 vma->vm_start (应用层的UVA) 映射到 phy_addr (物理地址) if (remap_pfn_range(vma, vma->vm_start, // 用户虚拟地址起始 phy_addr >> PAGE_SHIFT, // 物理地址页帧号 vma->vm_end - vma->vm_start, // 映射大小 vma->vm_page_prot)) // 属性:无缓存 { return -EAGAIN; } return 0; }4. 关键细节与注意事项
4.1 为什么要pgprot_noncached?
- 原因:对于硬件寄存器,必须禁止 CPU 缓存 (Cache)。
- 后果:如果不加这一行,应用程序写数据可能只写到了 Cache 里,LED 灯根本不会亮,或者读取的状态是旧的。
4.2 为什么映射大小通常是 4KB (4096)?
- MMU 管理内存的最小单位是一页 (Page),ARM Linux 默认页大小是 4096 字节。
- 即使你只需要操作 4 个字节的寄存器,
mmap也会映射整整一页(4096 字节)。因此,你在计算偏移量时要小心。
4.3ioremap和mmap的关系
- 驱动程序自己要访问寄存器 -> 用
ioremap。 - 驱动程序想让应用程序直接访问寄存器 -> 实现
.mmap接口(内部调用remap_pfn_range)。 - 通常一个驱动里这两个都会用到。
5. 总结
- 物理地址是唯一的真理,但被 CPU 藏在了 MMU 后面。
- KVA (内核虚拟地址)是驱动在内核态用的,通过
ioremap映射。 - UVA (用户虚拟地址)是应用在用户态用的,通过
mmap系统调用请求,由驱动协助映射。 remap_pfn_range是连接 UVA 和 物理地址 的桥梁。
下一步实践建议:
在韦东山的开发板上,你可以写一个简单的程序,利用 /dev/mem 这个系统自带的驱动节点。它已经实现了 mmap 功能。你可以尝试用 mmap 映射 GPIO 的物理基地址,然后在应用层直接点亮 LED,这将是你理解这一概念最直观的实验。