Lazy loaded image
Words 0Read Time 1 min
Invalid Date
核心结论
mmap 的危险不只发生在建立映射那一刻,更发生在映射建立之后:用户还在访问,底层对象却可能已经被释放、复用、下电或移除。 生命周期管不好,前面的权限、缓存和一致性设计都会被一脚踹翻。

专题导航

概述

很多驱动在 .mmap 阶段看起来逻辑完整:参数校验有了、页属性设了、映射也成功了。但真正致命的问题常常发生在后面——用户态还在长期持有映射地址,而驱动已经开始回收对象、重配 buffer、热拔插设备或卸载模块。
 
因此,mmap 从来不是一次性动作,而是一个跨越整个资源生命周期的契约

架构框架

分点细节

1. 为什么生命周期是 mmap 的后半场

用户态拿到映射地址后,访问关系可能持续:
  • 数秒
  • 整个进程生命周期
  • 设备运行的整个会话期
 
也就是说,驱动不能再把这块底层内存当成“内部临时资源”随便处理。

2. 典型危险场景

用户仍在访问,buffer 已被释放
用户态还拿着旧映射地址,底层页却已归还或复用。这会导致:
  • 读到脏数据
  • 写坏别的对象
  • 问题呈现为随机异常
设备 remove 或热拔插
硬件资源已经不存在,但用户映射关系还在;如果没有失效处理,后果通常相当难看。
驱动模块准备卸载
如果没有引用关系保护,模块代码都准备退场了,旧 VMA 还在触发访问,这属于主动给自己制造惊喜。

3. 生命周期绑定的核心目标

驱动要保证:
  • 映射存在期间,底层对象仍然有效
  • 对象失效前,访问路径会被收口或标记不可用
  • 清理顺序明确,不会出现双重释放或悬挂引用

4. 常见管理手段

引用计数
最基础也最常见,用来保证底层对象存活时间不短于映射时间。
vm_operations_struct
可以结合 VMA 的:
  • open
  • close
  • fault
跟踪映射实例数量、关联对象和释放时机。
显式失效标志
在设备 remove、异常恢复或 buffer 重配时,将对象标记为不可继续访问,再配合用户态协议完成收敛。

5. mmap 资源绝不是“驱动私有临时变量”

一旦资源被用户态映射,它就变成了跨边界共享资产。你要显式定义:
  • 谁拥有释放权
  • 何时可以重配
  • 何时必须等待用户退出
  • 异常路径如何兜底

实践指南

为每类映射对象设计生命周期状态机

最少应有以下状态:
  • 未映射
  • 已映射使用中
  • 准备失效
  • 已失效待清理
 
状态先画出来,代码才不容易写成迷宫。

把进程退出、munmap、设备 remove 一起考虑

很多驱动只处理正常路径,却忘了:
  • 用户进程异常退出
  • 设备热拔插
  • 驱动 probe 失败后的回滚
  • 运行中 buffer 重新分配
 
真正稳的代码,难点从来不在 happy path。

对用户接口提供失效感知机制

如果映射对象可能因设备状态变化而失效,建议配套:
  • poll / eventfd / 中断通知
  • 状态寄存器或共享状态区
  • 明确的错误码与重试策略

易错点分析

  • 映射成功后就不再追踪对象引用:这是生命周期事故的经典起点。
  • free 写在驱动关闭路径里就以为结束了:用户态可能还在访问。
  • 设备 remove 时直接回收全部资源:没给 VMA 留退场通道。
  • 只考虑正常退出,不考虑异常退出和热插拔:真实世界最爱挑你没写的路径出手。
  • 没有状态机,只靠 if-else 拼凑清理逻辑:维护几轮后,团队没人敢改。

词汇表

  • 生命周期:对象从创建、使用到失效和回收的全过程。
  • 悬挂映射:用户态仍持有地址,但底层对象已无效的映射状态。
  • 引用计数:用于追踪对象是否仍被使用的计数机制。
  • 热拔插 / remove:设备运行期间被移除或失效的场景。
  • 失效处理:在对象不能继续使用时收敛访问并完成回收的机制。

关联和跳转

上一篇
Data Structure and Algorithm
下一篇
用面试拷问嵌入式技术栈

Comments
Loading...