Linux内核模块卸载时运行代码是否会被中断?引用计数该用吗?
模块卸载与清理函数的安全保障问题解答
一、清理函数执行时发起rmmod,内核会中断它吗?
内核不会直接中断正在执行的清理函数,而是会等待其执行完成后再继续卸载流程,核心逻辑如下:
- 当调用
SYS_delete_module(即rmmod触发)时,内核首先会持有*module_mutex*(全局模块互斥锁),这个锁会阻止其他模块操作同时进行,但不会中断已在运行的函数。 - 接下来内核会检查模块的状态,若模块处于
MODULE_STATE_GOING(正在卸载)或MODULE_STATE_UNFORMED,会进一步调用wait_for_module等待模块退出。 - 关键的检查逻辑:内核会遍历所有CPU,查看当前执行的指令指针(IP)是否落在该模块的内存范围内。如果存在CPU正在执行模块内的代码(包括你的清理函数),内核会循环等待,直到所有CPU都离开模块内存区域,才会继续后续的卸载(比如释放模块内存)。
你看到的两种LLM说法其实并不矛盾:*检查指令指针+等待*是具体的等待逻辑,而*全局互斥锁*是用来保护模块操作的并发安全,二者是卸载流程中不同环节的机制。
二、是否可以通过引用计数保障安全?应该这么做吗?
可以添加引用计数,且这是内核模块安全设计的标准实践之一
引用计数的核心作用是跟踪模块资源的使用情况,确保在资源被使用时,模块不会被卸载。具体实现方式:
- 在模块初始化时初始化引用计数(比如用
struct module自带的refcnt,或者自定义计数)。 - 当你的清理函数开始执行时,增加引用计数;执行完成后减少计数。
- 在
__exit函数中,需要等待引用计数降为0后再执行清理(如果清理逻辑需要),或者结合现有全局标志避免重复执行。
为什么应该这么做?
- 补充内核原有机制的覆盖范围:内核的
wait_for_module只能检测CPU是否正在执行模块代码,但如果你的清理函数涉及异步资源(比如延迟工作队列、未完成的IO),仅靠内核的等待机制无法覆盖,引用计数可以跟踪这类场景。 - 避免潜在的竞态:即使内核等待CPU离开模块内存,若清理函数中存在可能被其他路径触发的逻辑(比如中断上下文调用),引用计数可以确保清理完成前模块不会被卸载。
- 符合内核编码规范:内核模块的安全设计中,引用计数是最常用的资源同步手段之一,比单纯的全局标志更可靠(全局标志容易出现竞态,需要额外的锁保护)。
注意事项
- 不要重复造轮子:优先使用内核提供的
try_module_get()和module_put()操作模块自带的引用计数,这些函数已经处理了原子操作和状态检查。 - 确保计数的正确增减:在所有可能触发清理的路径(包括主动调用和
__exit)中,都要正确维护引用计数,避免泄漏或悬空引用。
内容的提问来源于stack exchange,提问作者Programmer_2147483647




