当前位置: 首页 > news >正文

驱动模块的加载与卸载机制

昨天调板子又遇到个怪事:insmod加载驱动一切正常,但rmmod死活卸载不掉,内核日志里只留下一行“Device or resource busy”。查了半小时才发现,原来是有个用户态进程没关,一直占着驱动文件。这种问题在嵌入式开发里太常见了,今天咱们就深挖一下驱动模块的加载卸载到底是怎么玩的。

模块加载那点事儿

写驱动第一件事就是弄明白怎么让内核认识你的代码。编译成.ko文件只是开始,真正的魔法在insmod执行那一刻。内核会先检查模块的ELF头,找到.init.text段——这里头放的就是模块初始化函数。对了,你的module_init宏定义其实就是把函数指针塞到这个段里。

加载过程最关键的其实是符号解析。内核维护着一张符号表,里面是所有导出的函数和变量。你的驱动要是调了printk,加载时就得去这张表里找到printk的地址。如果找不到,insmod直接报“Unknown symbol”,这时候就得检查Kconfig里依赖项配对了没。我常犯的错是忘了EXPORT_SYMBOL,自己写的函数别的模块用不了,排查起来特别费劲。

内存分配也讲究。内核给模块代码段和数据段分配内存时,用的是vmalloc区。这个区域地址不连续,但能灵活分配大块内存。所以驱动里那些静态全局变量,其实都在这个区域里待着。

卸载时的清理艺术

卸载比加载麻烦多了。rmmod触发的是你module_exit注册的函数,但内核可不会立马把模块内存释放掉。它得先确认没有进程还在用这个驱动——检查引用计数。就是那个struct module里的refcnt字段,每次open设备文件它加1,close时减1。只有减到0了,内核才允许卸载。

引用计数这玩意儿容易出坑。我早年写驱动就犯过低级错误:在probe函数里加了引用,在remove函数里却忘了减。结果设备热插拔几次后,模块再也卸不掉了。现在我都习惯在代码里加个debugfs节点,随时能查引用计数。

资源释放得倒着来。谁后申请的先释放,像套娃一样一层层拆。一般是先注销设备号,再销毁cdev,接着释放内存,最后把sysfs节点清理干净。顺序乱了可能不会马上出错,但会在内核里留下脏数据,跑久了系统就不稳定。

实战中的那些坑

有一次给客户调试,他们的驱动卸载函数里直接调用了kfree,但指针在别的地方已经被置NULL了。内核没崩溃,但kmemleak检测到了内存泄漏。这种问题最难查,因为表面看起来一切正常。后来我养成了习惯:在kfree前加个if (ptr)判断,虽然多写一行,但省了无数调试时间。

还有符号版本问题。不同内核版本导出的函数签名可能微调,比如参数从int改成size_t。你的驱动在A版本能加载,B版本就报错。解决办法是用MODULE_INFO(vermagic, …)指定内核版本范围,或者直接检查LINUX_VERSION_CODE。

热插拔场景更头疼。设备突然被拔掉,驱动可能还在执行某个IOCTL。这时候卸载函数得处理半路中断的操作。我的经验是加个完成量(completion),让正在进行的操作有机会优雅退出。粗暴地直接return -EBUSY虽然简单,但用户体验太差。

调试技巧分享

遇到卸载失败,先看/proc/modules里模块的引用计数是不是0。如果不是,lsof /dev/你的设备节点 能告诉你哪个进程还占着。内核配置打开CONFIG_MODULE_DEBUG后,还能看到更详细的加载卸载日志。

动态打印很有用。在exit函数里加pr_debug,通过sysfs调整动态调试级别,不用重新编译就能看日志。比printk灵活,生产环境也能开着。

写个脚本自动化测试加载卸载循环,跑个几百次。很多竞态条件都是重复操作几十次后才出现的。我一般在Makefile里加个test目标,用while循环跑insmod/rmmod,配合dmesg -w抓异常。

个人经验之谈

驱动模块不是应用程序,没有main函数那种明确的入口出口。它更像是给内核打补丁,加载时把代码缝进内核空间,卸载时得拆得干干净净。最怕的就是拆完后留下线头——那些没释放的资源就是内核里的线头,迟早绊倒系统。

新手常纠结于功能实现,老手更关注生命周期管理。好的驱动应该像客人轻轻离开房间,不留一丝痕迹。每次写exit函数时,我都假设这个函数可能因为各种原因被调用:正常rmmod、设备突然消失、甚至系统休眠唤醒。多想想异常路径,代码才够健壮。

最后给个实在的建议:把你写的每个驱动都当成会被别人在半夜三点调用。那时候可没人在线帮你查问题,所以日志要清晰,错误处理要周全,卸载要彻底。内核开发这行,严谨比聪明更重要。

http://www.jsqmd.com/news/582655/

相关文章:

  • 008、队列(Queue):任务间通信的基石
  • Redis Sentinel 高可用方案在WMS仓储管理系统的应用
  • 虚拟组网工具 内网穿透神器 tailscale汉化中文安卓版和Magisk版
  • 关系型数据库星型模型聚合表生成
  • kprobe函数入口时的汇编跳板执行流程与栈帧机制
  • OpenCV图像处理——存储结构 Mat (Matrices)(版本 4.12.0)
  • 抢答器软件哪家强?五款抢答器软件全方位深度评测
  • 【数据手册解读15】贴片电感
  • 操作系统与数据库系统的核心知识点,属于计算机科学与技术专业(尤其是考研408统考或相关课程)的重点复习提纲
  • 资深大模型工程师详细讲解:RAG召回率优化三重微调实战
  • 提升数据采集效率:用快马平台快速生成高性能openclaw抓取脚本
  • 2026年压铸铝件厂家哪家好,铝压铸/铝合金压铸/压铸铝件/锌铝压铸/铝合金高压压铸/铝压铸件,压铸铝件企业联系电话 - 品牌推荐师
  • 【研报280】汽车轻量化材料研究报告:改性塑料的应用趋势
  • 基于MATLAB的信号调制与调解
  • Spring Boot + Vue 前后端联调踩坑记录
  • FIFA 23 Live Editor终极指南:10分钟掌握实时游戏修改技巧
  • 手把手教程:快速设置远程开机,看完就会
  • 每日 200 篇免费额度!PaperXie 查重:把论文安全感焊死在毕业季
  • 2026年五星酒店床垫推荐:五家优选品牌深度解析 - 科技焦点
  • Windows环境下安装TVM编译器
  • 5大核心优势:为多场景用户打造的屏幕翻译解决方案
  • 【头歌】操作系统 课堂练习2.3:系统调用
  • OpenMS实战指南:如何用开源工具解决质谱数据分析三大难题
  • 春游出发前买酒外卖来得及吗?歪马送酒大额券解锁春日微醺新方式 - 资讯焦点
  • 论文查重还在花冤枉钱?Paperxie 免费查重,本科生的毕业省钱神器
  • SQL优化让查询提升10倍——从数据库工程到执行计划深度解析
  • 2026海外网红营销内容合作与策划最佳实践
  • 数据分析之事实表(Fact Table)
  • 代码随想录算法训练营第一天 | Leetcode 704.二分查找 | Leetcode 27.移除元素 | Leetcode 977.有序数组的平方 (c#和c++双语)
  • 履约门槛再次大修!TikTok美区全面强制官方物流后,卖家该怎样守住前台账号的安全底线?