内核模块引用计数
当我加载了一个内核模块.ko文件,我想获取这个内核模块的引用计数
方法一:
cat /sys/module/你的模块名/refcnt
方法二:
用 lsmod 或者 modinfo
首先我有两个内核模块myprint.ko和use.ko,其中use.ko中使用了myprint.ko模块中的一个 value 值,二者源代码如下:
//myprint.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int num; /* 默认值 0 */
module_param(num, int, 0644); /* 名称, 类型, 权限 */
MODULE_PARM_DESC(num, "An integer to print from kernel space");
static int __init myprint_init(void)
{
pr_info("myprint: loading module, num=%d\n", num);
return 0;
}
static void __exit myprint_exit(void)
{
pr_info("myprint: unloading module, num was %d\n", num);
}
int myprint_value = 42;
EXPORT_SYMBOL_GPL(myprint_value);
module_init(myprint_init);
module_exit(myprint_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sea");
MODULE_DESCRIPTION("Print a user-supplied integer via module_param");
//use.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
extern int myprint_value;
static int __init use_init(void)
{
/* 只要这里引用了 myprint 的符号,链接器就会生成
"use.ko 依赖 myprint.ko" 的模块依赖关系 */
pr_info("use: myprint_value = %d\n", myprint_value);
return 0;
}
static void __exit use_exit(void)
{
pr_info("use: bye\n");
}
module_init(use_init);
module_exit(use_exit);
MODULE_LICENSE("GPL");
使用lsmod查看两个内核模块:
sea@fanbao:~/module_test$ lsmod
Module Size Used by
use 16384 0
myprint 16384 1 use
sea@fanbao:~/module_test$ lsmod | grep myprint
myprint 16384 1 use
sea@fanbao:~/module_test$ lsmod | grep use
use 16384 0
myprint 16384 1 use
可以看出myprint引用计数为1,被use使用
使用modinfo查看:
sea@fanbao:~/module_test$ modinfo ./myprint/myprint.ko
filename: /home/sea/module_test/./myprint/myprint.ko
description: Print a user-supplied integer via module_param
author: Sea
license: GPL
srcversion: 99C995047E46A692D88BE70
depends:
retpoline: Y
name: myprint
vermagic: 5.15.189 SMP mod_unload modversions
parm: num:An integer to print from kernel space (int)
sea@fanbao:~/module_test$ modinfo ./use/use.ko
filename: /home/sea/module_test/./use/use.ko
license: GPL
srcversion: EAE124624F51A24838B86A4
depends: myprint
retpoline: Y
name: use
vermagic: 5.15.189 SMP mod_unload modversions
可以看出myprint被use使用
内核模块引用计数过程分析
总览
加载模块:

卸载模块:

不涉及依赖时的模块加载过程解析
加载内核模块系统调用对应的内核函数如下:
init_module
finit_module
其中init_module是调用函数copy_module_from_user从用户态拷贝内核模块到内核态,finit是调用函数kernel_read_file_from_fd从文件系统读取内核模块,然后它们都会调用函数load_module加载内核模块
你执行 insmod 加载一个内核模块时,从模块被加载到执行模块第一条指令(即模块的 __init 函数),在到整个模块初始化完成(即内核模块中的 __init函数执行完毕),模块的引用计数(refcnt)经历了以下几个关键变化:
✅ 阶段一:模块加载初期(load_module())
- 引用计数初始化为 MODULE_REF_BASE(通常是 1)
在 module_unload_init() 中:
这是模块的“基础引用”,表示模块已被加载,但尚未初始化完成。atomic_set(&mod->refcnt, MODULE_REF_BASE);
✅ 阶段二:模块初始化前(do_init_module())
- 引用计数增加 1(防止初始化期间被卸载)
在 do_init_module() 开始时:
这是为了防止模块在初始化过程中被其他线程通过 rmmod 卸载。atomic_inc(&mod->refcnt);
✅ 阶段三:模块初始化完成(do_init_module() 末尾,do_one_initcall之后)
- 引用计数减少 1(释放初始化时的保护引用)
在 do_init_module() 末尾:module_put(mod);
此时模块已初始化完成,状态变为 MODULE_STATE_LIVE,引用计数回到 MODULE_REF_BASE,表示模块现在是“可被卸载”的状态(即没有其他引用时可以被卸载)。
✅ 具体函数走向如下
load_module -> module_unload_init
| |
| |————————-->atomic_set 设置refcnt初始值为1
| |————————-->atomic_inc 防止初始化模块时被卸载,令refcnt+1
|
|——————————————--> do_init_module
|
|————————--> do_one_initcall 开使从自己模块的第一条代码执行
|————————--> module_put 当模块执行完毕,使引用计数-1
关于引用计数的流程图:
| 阶段 | 引用计数变化 | 说明 |
|---|---|---|
| 模块加载完成 | 设为 1 | 模块存在,但未初始化 |
| 开始初始化 | +1 → 2 | 防止初始化期间被卸载 |
| 初始化完成 | -1 → 1 | 回到基础引用,模块可被卸载 |
下面通过一个死循环的模块实例展示引用计数:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h> /* msleep */
static int __init grab_init(void)
{
/* 死循环:每 1 秒打印一次 refcnt */
while (1) {
pr_info("grab: refcnt = %d\n",
module_refcount(THIS_MODULE)); //THIS_MODULE宏,表示当前模块的结构体
ssleep(1); /* 睡眠 1 秒 */
}
return 0;
}
static void __exit grab_exit(void)
{
pr_info("grab: exit\n");
}
module_init(grab_init);
module_exit(grab_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Dead-loop module to print refcnt every second");
实验结果如下:
[ 5425.283152] grab: refcnt = 1
[ 5426.306868] grab: refcnt = 1
[ 5427.330931] grab: refcnt = 1
[ 5428.354917] grab: refcnt = 1
[ 5429.378941] grab: refcnt = 1
[ 5430.403075] grab: refcnt = 1
[ 5431.426958] grab: refcnt = 1
[ 5432.450937] grab: refcnt = 1
...
问题出现了,因为模块没有退出,所以 do_one_initcall 函数不会返回,因此不会执行后面的 module_put ,因此此时的 refcnt 值应该是2才对
因为我们是用函数 module_refcount 来返回 refcnt 的值,其中 module_refcount 的函数如下,可以看出返回的值已经把 MODULE_REF_BASE 减去了,所以通过这个函数返回的值相当于净引用数,而不是原始计数器,而这个净引用数和文件 /sys/module/<模块name>/refcnt 中的值是一样的:
int module_refcount(struct module *mod)
{
return atomic_read(&mod->refcnt) - MODULE_REF_BASE;
}
EXPORT_SYMBOL(module_refcount);
sea@fanbao:~/module_test/grab/grab1$ cat /sys/module/grab/refcnt
1
所以
| 阶段 | atomic_read(&refcnt) | module_refcount(mod) 或 /sys/module/name/refcnt |
|---|---|---|
| 加载完成 | 1 | 0 |
| 初始化中(+1) | 2 | 1 |
| 初始化完成(-1) | 1 | 0 |
| 有其他模块引用 | 2 或更多 | 1 或更多 |
手动增加引用计数
__module_get
void __module_get(struct module *module)
{
if (module) {
preempt_disable();
atomic_inc(&module->refcnt); //无条件 + 1
trace_module_get(module, _RET_IP_); //trace日志
preempt_enable();
}
}
EXPORT_SYMBOL(__module_get);
try_module_get
bool try_module_get(struct module *module)
{
bool ret = true;
if (module) {
preempt_disable();
/* Note: here, we can fail to get a reference */
if (likely(module_is_live(module) && //判断moule不是going状态
atomic_inc_not_zero(&module->refcnt) != 0)) //且引用计数不为0
trace_module_get(module, _RET_IP_);
else
ret = false;
preempt_enable();
}
return ret;
}
EXPORT_SYMBOL(try_module_get);
这段代码是内核里“安全地给模块加引用”的官方实现,函数 try_module_get() 的语义:“如果模块还活着,就把它的引用计数加 1;如果它已经死了,就什么也不做并返回 false。”
其中if (likely(module_is_live(module) && atomic_inc_not_zero(&module->refcnt) != 0))是核心实现:
-
module_is_live(module)
检查模块状态标志,不是在 going 状态返回1,在 going 状态返回0:static inline bool module_is_live(struct module *mod) { return mod->state != MODULE_STATE_GOING; }
其中模块状态有四种:
| 状态 | 含义 | 典型场景 |
|---|---|---|
| MODULE_STATE_UNFORMED | 模块刚被加载,还没准备好运行,仍在建立符号、重定位、创建 sysfs 等阶段。 | insmod 早期,layout_and_allocate() 之后到 complete_formation() 之前。 |
| MODULE_STATE_COMING | 模块已完全成形,正在执行 init 函数(mod->init())。此时不可卸载。 | do_init_module() 里调用 mod->init() 期间。 |
| MODULE_STATE_LIVE | 模块初始化成功,已投入正常运行,可以被使用,也可以被卸载。 | do_init_module() 成功返回后,直到有人发起 rmmod。 |
| MODULE_STATE_GOING | 模块正在卸载,正在执行 exit 函数或正在清理资源。 | delete_module() 开始后,直到内存全部释放完毕。 |
- atomic_inc_not_zero(&module->refcnt)
原子地把 refcnt 加 1,但前提是当前引用值不为 0。其中 atomic_inc_not_zero 是对 宏 arch_atomic_inc_not_zero 的封装,这个宏根据架构不同也有不同的实现,下面以x86架构为例
//arch/powerpc/include/asm/atomic.h
/**
* atomic_inc_not_zero - increment unless the number is zero
* @v: pointer of type atomic_t
*
* Atomically increments @v by 1, so long as @v is non-zero.
* Returns non-zero if @v was non-zero, and zero otherwise.
*/
static __inline__ int arch_atomic_inc_not_zero(atomic_t *v)
{
int t1, t2;
__asm__ __volatile__ (
PPC_ATOMIC_ENTRY_BARRIER
"1: lwarx %0,0,%2 # atomic_inc_not_zero\n\
cmpwi 0,%0,0\n\
beq- 2f\n\
addic %1,%0,1\n"
" stwcx. %1,0,%2\n\
bne- 1b\n"
PPC_ATOMIC_EXIT_BARRIER
"\n\
2:"
: "=&r" (t1), "=&r" (t2)
: "r" (&v->counter)
: "cc", "xer", "memory");
return t1;
}
#define arch_atomic_inc_not_zero(v) arch_atomic_inc_not_zero((v))
strong_try_module_get
/*
* We require a truly strong try_module_get(): 0 means success.
* Otherwise an error is returned due to ongoing or failed
* initialization etc.
*/
static inline int strong_try_module_get(struct module *mod)
{
BUG_ON(mod && mod->state == MODULE_STATE_UNFORMED);
if (mod && mod->state == MODULE_STATE_COMING)
return -EBUSY;
if (try_module_get(mod))
return 0;
else
return -ENOENT;
}
__module_get 和 try_module_get 区别
| 对比点 | __module_get() | try_module_get() |
|---|---|---|
| 是否检查模块状态 | ❌ 不检查 | ✅ 检查 module_is_live() |
| 是否检查引用计数 | ❌ 不检查 | ✅ 用 atomic_inc_not_zero() 保证计数 ≠ 0 |
| 是否可能失败 | ❌ 永远成功 | ✅ 可能返回 false |
| 典型用途 | 内核内部已知安全的代码路径 | 外部代码不确定模块是否存活时使用 |
| trace 触发条件 | 只要调用就触发 | 仅当成功加 1 时触发 |
strong_try_module_get 和 try_module_get 区别
| 函数名 | 返回类型 | 成功执行时模块状态 |
|---|---|---|
| strong_try_module_get | 成功 → 0;失败 → 负 errno(-EBUSY/-ENOENT) | 只能当模块处于 MODULE_STATE_LIVE 时 |
| try_module_get | 成功 → true;失败 → false | 只要模块不处于 MODULE_STATE_GOING |

浙公网安备 33010602011771号