内核模块引用计数

当我加载了一个内核模块.ko文件,我想获取这个内核模块的引用计数

方法一:

cat /sys/module/你的模块名/refcnt

方法二:
用 lsmod 或者 modinfo

首先我有两个内核模块myprint.kouse.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() 开始时:
      atomic_inc(&mod->refcnt);
    
    这是为了防止模块在初始化过程中被其他线程通过 rmmod 卸载。

✅ 阶段三:模块初始化完成(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
posted @ 2025-08-14 13:42  爱吃鸡魔人zf  阅读(18)  评论(0)    收藏  举报