驱动模块的加载与卸载机制
昨天调板子又遇到个怪事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设备文件它加1close时减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.coloradmin.cn/o/2479805.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!