核心结论
mmap 在驱动里的执行链路,本质不是“内核帮你返回一个地址”,而是:用户态发起映射请求 → 内核建立 VMA 描述 → 驱动 .mmap 做策略决策 → 页表把底层页或页框映射进用户虚拟地址空间。专题导航
- 专题总览:Linux 驱动专题 - 详解 mmap
概述
对于嵌入式内核与驱动开发者来说,理解
mmap 的关键不在于系统调用原型,而在于请求是如何一路落到驱动,并最终变成页表映射。这一链路决定了你该在哪一层做范围校验、权限控制、页属性设置和生命周期绑定。如果只知道
file_operations 里有个 .mmap,却说不清 VMA、页属性和具体映射方式,代码大概率也会写成“能跑但不稳”的试验品。架构框架
分点细节
1. 用户态 mmap() 只是提出映射需求
典型调用如下:
这里用户态声明了几件事:
- 想映射多大
- 以什么权限访问
- 是共享还是私有
- 从设备对象的哪个偏移开始映射
但真正是否允许、映射到什么对象、用什么页属性,决定权不在用户态,而在内核和驱动。
2. 内核先建立 VMA,再把策略控制权交给驱动
vm_area_struct 表示进程地址空间中的一段虚拟内存区域,里面描述了:- 起止虚拟地址
- 访问权限
- 页保护属性
- 关联文件对象
- 可选的
vm_ops
你可以把 VMA 理解成“这段映射关系的合同文本”。驱动后面做的所有处理,基本都围绕这份合同展开。
3. 驱动 .mmap 是策略落点
在字符设备场景下,最后会进入驱动的
.mmap 回调。这里通常要完成:- 校验
offset与size是否落在允许范围内
- 判断映射对象究竟是寄存器、DMA buffer 还是共享页
- 调整
vma->vm_page_prot
- 按对象类型选择建立映射的方法
这一层的职责不是“把指针交给用户”,而是决定页表应该如何合法地建立。
4. 常见映射建立方式
remap_pfn_range()
适合把一段物理页框直接映射到用户空间,常见于:
- MMIO 寄存器窗口
- 连续物理内存
- 部分 framebuffer / DMA 场景
vm_insert_page() / vmf_insert_*
适合驱动手里已经持有
struct page 的情况,更适合页级粒度插入。vm_operations_struct
当你需要更复杂的缺页处理或生命周期管理时,可以结合:
open
close
fault
这类设计常见于按页懒建立映射,或者需要精细追踪资源持有关系的驱动。
5. 真正被建立的是用户虚拟地址到目标页的映射关系
这一点非常关键:
- 用户拿到的是用户虚拟地址
- 不是内核把某个裸指针直接塞给用户
- 也不是做了一份数据拷贝
一旦页表建立完成,后续大多数数据访问都不再经过
read/write 的复制路径,这也是 mmap 高性能与高风险并存的原因。实践指南
先画清楚你的映射链路
动手前先回答以下问题:
- 用户传进来的
offset对应哪一类对象?
- 是整段一次性映射,还是按页处理?
- 用户态访问的是数据面还是控制面?
- 这段映射之后,谁负责释放和失效处理?
在 .mmap 中保持职责单一
更稳的实践是把
.mmap 划成几步:- 参数校验
- 对象分发
- 页属性设置
- 建立映射
- 绑定生命周期
别把业务逻辑、权限判断、调试开关和映射细节揉成一锅,这种代码通常第二年就没人敢碰。
为不同对象准备独立映射函数
例如:
map_regs_to_user()
map_dma_buffer_to_user()
map_shared_pages_to_user()
这样不仅清晰,也方便后续对不同对象附加不同的属性和限制。
易错点分析
- 把
.mmap理解成返回内核指针:这是概念错误,真正建立的是页表映射。
- 只校验长度,不校验偏移:用户一旦能跳到未授权窗口,安全边界就废了。
- 默认页属性不管:很多诡异问题就埋在这里,尤其是寄存器和 DMA。
- 不区分一次性映射与缺页驱动映射:复杂对象的生命周期会因此失控。
- 映射成功就以为结束了:实际上后面的同步、失效、回收才是长期战场。
词汇表
- VMA:
vm_area_struct,进程地址空间中的一段虚拟内存描述。
- PFN:Page Frame Number,物理页框号。
remap_pfn_range():将物理页框范围映射到用户虚拟地址空间的常用接口。
vm_insert_page():将具体页对象插入用户 VMA 的接口。
vm_operations_struct:与 VMA 生命周期和缺页处理相关的操作集合。
关联和跳转
- 如果你还没先搞清“映射对象”,先回到:01|驱动 mmap 到底在映射什么?先把对象讲对
- 如果链路看懂了,下一步必须看页属性:03|缓存属性:为什么 mmap 最阴的坑常常不是代码,而是 cache
- 如果映射对象是 DMA buffer,继续阅读:
- 如果你要做资源回收和设备移除处理,直接看:06|生命周期:用户还在用,底层内存就不能先死






