
如果说模块加载函数是 "开店营业",那卸载函数就是 "关店打烊"—— 后者看似简单,实则关系到系统稳定性。很多开发者重视加载逻辑却忽视卸载过程,结果导致内存泄漏、设备卡死等问题。今天咱们就彻底搞懂模块卸载函数,学会让模块 "优雅退场"。
模块卸载函数是当你执行rmmod命令时,内核自动调用的 "收尾函数"。它的核心任务就一个:清理加载函数申请的所有资源,把系统恢复到模块加载前的状态。
举个生活化的例子:加载函数像你出差入住酒店时的 "登记 + 领房卡",卸载函数就是 "退房 + 交钥匙 + 结清费用"。如果退房时没交钥匙(没释放资源),下次就没人能再用这个房间了(设备无法再次加载)。
和加载函数相比,卸载函数有三个明显不同:
void类型),因为卸载失败也无法挽回__exit宏标记,告诉内核这是卸载专用函数static void __exit 函数名(void) {
// 释放资源的操作
}
module_exit(函数名); // 告诉内核这是卸载函数这个结构里的每个部分都有特殊意义:
static:和加载函数一样,限制函数仅在当前模块可见__exit:内核会把带这个标记的函数存放在.exit.text段,只有模块支持卸载时才保留module_exit:注册卸载函数,让内核知道模块卸载时该调用谁__exit宏的特殊作用__exit宏不仅仅是个标记,它会影响内核对函数的处理:
CONFIG_MODULE_UNLOAD=n(禁用卸载),带__exit的函数会被直接丢弃__exit函数在执行后会被从内存中移除当执行rmmod mymod时,整个过程像一场 "有序撤离":

关键步骤解析:
module->refcnt引用计数判断模块是否被使用(比如有进程打开了模块创建的设备文件)。如果refcnt > 0,会提示 "资源正忙"(-EBUSY),卸载失败。
/proc/modules列表中移除该模块,lsmod再也查不到它的信息,就像 "从未存在过"。
卸载函数的核心使命是 "清理战场",以下这些资源必须逐个释放:
1. 设备驱动相关资源
unregister_chrdev或cdev_delunregister_blkdevunregister_netdev2. 内存资源
kfree释放kmalloc申请的内存vfree释放vmalloc申请的内存kfree_slab释放 slab 缓存3. 中断与定时器
free_irq释放申请的中断号del_timer_sync删除定时器并等待其结束4. 其他资源
remove_proc_entrykthread_stop)实例:正确的释放顺序
// 加载函数(简化)
static int __init demo_init(void) {
buf = kmalloc(1024, GFP_KERNEL); // 步骤1:申请内存
register_chrdev(0, "demo", &fops); // 步骤2:注册设备
return 0;
}
// 卸载函数(反向释放)
static void __exit demo_exit(void) {
unregister_chrdev(dev_num, "demo"); // 先注销设备(步骤2的反向)
kfree(buf); // 再释放内存(步骤1的反向)
}这是最常见的错误,原因通常有两个:
lsmod查看Used by字段)解决步骤:
lsof /dev/demo查看谁在使用设备rmmod表现为:多次加载卸载后,free命令显示可用内存越来越少。
排查技巧:
slabtop查看内核内存使用,找是否有持续增长的 slab 缓存printk日志,确认每个释放函数都被执行通常是因为:
kfree同一个指针两次)buf为NULL时调用kfree)解决办法:
BUG_ON检查指针有效性:BUG_ON(!buf); kfree(buf);static void __exit demo_exit(void) {
if (dev_registered) { // 用标志位判断是否需要注销
unregister_chrdev(dev_num, "demo");
}
if (buf) { // 检查指针非空再释放
kfree(buf);
buf = NULL; // 避免重复释放
}
}在模块中定义标志位,记录哪些资源已成功初始化:
static bool dev_registered = false;
static bool buf_allocated = false;
// 加载函数中
buf = kmalloc(...);
if (buf) buf_allocated = true;
// 卸载函数中
if (dev_registered) {
unregister_chrdev(...);
dev_registered = false;
}卸载函数只做 "释放",不做 "创建" 或复杂逻辑:
kmalloc(可能失败)模块卸载函数看似简单,实则是模块稳定性的 "最后一道防线"。记住这三个核心原则:
一个好的卸载函数,应该让模块像 "从未存在过" 一样干净地离开系统。下次写模块时,不妨多花点时间打磨卸载逻辑 —— 毕竟,能优雅退场的模块才是真正可靠的模块。