struct module 深度解析:内核模块的核心数据结构¶
适用平台:i.MX6ULL (ARM Cortex-A7) 内核版本:Linux 6.12.49 (linux-imx) 目标读者:准备入门驱动开发的嵌入式工程师 前置知识:已阅读前面章节,了解模块加载基本流程
为什么要写这一章¶
在前面几章中,我们学习了模块的基本概念、编写方法和加载流程。但是,有一个核心问题我们还没有深入探讨:内核到底是用什么数据结构来描述一个模块的?
struct module 是内核模块系统的核心数据结构。理解它的每一个字段,不仅能帮你更好地理解模块的工作原理,还能在调试问题时快速定位原因。比如,当你遇到"Module is in use"错误时,你知道是 refcnt 字段在作怪;当你需要在内核中遍历所有模块时,你知道 modules 链表就是入口。
这一章,我们结合 linux-imx 6.12.49 的实际源码,对 struct module 进行一次彻底的剖析。
一、rmmod 底层全流程(补充)¶
在深入 struct module 之前,让我们先把模块卸载的完整流程理清楚,这样能更好地理解后续的字段含义。
1.1 delete_module 系统调用¶
当你执行 rmmod 命令时,最终会调用 delete_module 系统调用。这个系统调用的实现在 kernel/module/main.c:700:
/* third_party/linux-imx/kernel/module/main.c:700 */
SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
unsigned int, flags)
{
struct module *mod;
char name[MODULE_NAME_LEN];
char buf[MODULE_FLAGS_BUF_SIZE];
int ret, len, forced = 0;
/* 1. 权限检查:需要 CAP_SYS_MODULE 权限 */
if (!capable(CAP_SYS_MODULE) || modules_disabled)
return -EPERM;
/* 2. 从用户空间复制模块名 */
len = strncpy_from_user(name, name_user, MODULE_NAME_LEN);
if (len == 0 || len == MODULE_NAME_LEN)
return -ENOENT;
if (len < 0)
return len;
/* 3. 审计日志 */
audit_log_kern_module(name);
/* 4. 获取模块互斥锁 */
if (mutex_lock_interruptible(&module_mutex) != 0)
return -EINTR;
/* 5. 查找模块 */
mod = find_module(name);
if (!mod) {
ret = -ENOENT;
goto out;
}
/* 6. 检查是否有其他模块依赖我们 */
if (!list_empty(&mod->source_list)) {
/* Other modules depend on us: get rid of them first. */
ret = -EWOULDBLOCK;
goto out;
}
/* 7. 检查模块状态 */
if (mod->state != MODULE_STATE_LIVE) {
/* FIXME: if (force), slam module count damn the torpedoes */
pr_debug("%s already dying\n", mod->name);
ret = -EBUSY;
goto out;
}
/* 8. 检查是否有 exit 函数 */
if (mod->init && !mod->exit) {
forced = try_force_unload(flags);
if (!forced) {
/* This module can't be removed */
ret = -EBUSY;
goto out;
}
}
/* 9. 尝试停止模块(核心函数!) */
ret = try_stop_module(mod, flags, &forced);
if (ret != 0)
goto out;
/* 10. 释放锁,执行最后的清理 */
mutex_unlock(&module_mutex);
/* Final destruction now no one is using it. */
if (mod->exit != NULL)
mod->exit();
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_GOING, mod);
klp_module_going(mod);
ftrace_release_mod(mod);
async_synchronize_full();
/* Store the name and taints of the last unloaded module */
strscpy(last_unloaded_module.name, mod->name, sizeof(last_unloaded_module.name));
strscpy(last_unloaded_module.taints, module_flags(mod, buf, false), sizeof(last_unloaded_module.taints));
/* 11. 释放模块内存 */
free_module(mod);
/* someone could wait for the module in add_unformed_module() */
wake_up_all(&module_wq);
return 0;
out:
mutex_unlock(&module_mutex);
return ret;
}
1.2 try_stop_module 函数详解¶
try_stop_module() 是模块卸载的核心函数,负责处理引用计数和状态转换。实现在 kernel/module/main.c:668:
/* third_party/linux-imx/kernel/module/main.c:668 */
static int try_stop_module(struct module *mod, int flags, int *forced)
{
/* 如果模块还被引用,必须使用强制卸载 */
if (try_release_module_ref(mod) != 0) {
*forced = try_force_unload(flags);
if (!(*forced))
return -EWOULDBLOCK;
}
/* 标记模块为 GOING 状态 */
mod->state = MODULE_STATE_GOING;
return 0;
}
1.3 free_module 函数:模块内存释放¶
free_module() 负责释放模块占用的所有资源。实现在 kernel/module/main.c:1261:
/* third_party/linux-imx/kernel/module/main.c:1261 */
static void free_module(struct module *mod)
{
bool unload_codetags;
trace_module_free(mod);
/* 1. 检查并卸载代码标签 */
unload_codetags = codetag_unload_module(mod);
if (!unload_codetags)
pr_warn("%s: memory allocation(s) from the module still alive, cannot unload cleanly\n",
mod->name);
/* 2. 拆除 sysfs 接口 */
mod_sysfs_teardown(mod);
/* 3. 设置为 UNFORMED 状态 */
mutex_lock(&module_mutex);
mod->state = MODULE_STATE_UNFORMED;
mutex_unlock(&module_mutex);
/* 4. 架构特定的清理 */
module_arch_cleanup(mod);
/* 5. 模块卸载相关清理 */
module_unload_free(mod);
/* 6. 销毁模块参数 */
destroy_params(mod->kp, mod->num_kp);
/* 7. 如果是 livepatch 模块,释放 ELF 信息 */
if (is_livepatch_module(mod))
free_module_elf(mod);
/* 8. 从模块链表和树中移除 */
mutex_lock(&module_mutex);
list_del_rcu(&mod->list);
mod_tree_remove(mod);
module_bug_cleanup(mod);
synchronize_rcu();
if (try_add_tainted_module(mod))
pr_err("%s: adding tainted module to the unloaded tainted modules list failed.\n",
mod->name);
mutex_unlock(&module_mutex);
/* 9. 释放初始化段内存和参数 */
module_arch_freeing_init(mod);
kfree(mod->args);
percpu_modfree(mod);
/* 10. 释放模块内存 */
free_mod_mem(mod, unload_codetags);
}
1.4 强制卸载(rmmod -f)的风险¶
强制卸载(使用 O_TRUNC 标志)是非常危险的操作,可能导致:
- 系统崩溃:如果模块代码仍在执行
- 数据损坏:如果模块持有的数据结构还在被使用
- 资源泄漏:模块无法正常清理资源
内核文档 Documentation/kbuild/modules.rst 中明确警告不要使用强制卸载。
1.5 MODULE_STATE_GOING 状态处理¶
当一个模块进入 MODULE_STATE_GOING 状态时:
- 新的
try_module_get()调用会失败 - 内核不再允许其他模块依赖它
- sysfs 中的模块状态会更新
- 通知链会收到
MODULE_STATE_GOING通知
二、struct module 数据结构全字段解析¶
现在让我们逐字段分析 struct module 结构。这个结构定义在 include/linux/module.h:410:
/* third_party/linux-imx/include/linux/module.h:410 */
struct module {
enum module_state state;
struct list_head list;
char name[MODULE_NAME_LEN];
/* ... 更多字段 ... */
};
2.1 状态与标识字段¶
state:模块状态¶
enum module_state {
MODULE_STATE_LIVE, /* 正常运行状态 */
MODULE_STATE_COMING, /* 正在初始化 */
MODULE_STATE_GOING, /* 正在卸载 */
MODULE_STATE_UNFORMED, /* 还在设置中 */
};
状态转换图:
源码位置:include/linux/module.h:320
list:全局模块链表节点¶
这个字段将所有已加载的模块链接到全局的 modules 链表。定义在 kernel/module/main.c:76:
遍历所有模块的示例:
/* 遍历所有已加载模块 */
struct module *mod;
mutex_lock(&module_mutex);
list_for_each_entry(mod, &modules, list) {
pr_info("Module: %s, state: %d, refcnt: %d\n",
mod->name, mod->state,
atomic_read(&mod->refcnt));
}
mutex_unlock(&module_mutex);
name:模块名称¶
模块的唯一标识符,长度限制为 MAX_PARAM_PREFIX_LEN(通常为 64 字符)。
源码位置:include/linux/module.h:35、:417
2.2 符号导出字段¶
syms / num_syms:导出符号¶
这些字段描述模块导出的符号:
syms:导出符号数组(使用EXPORT_SYMBOL导出)crcs:对应的 CRC 校验值数组num_syms:导出符号的数量
struct kernel_symbol 定义(include/linux/export.h):
gpl_syms / num_gpl_syms:GPL 符号¶
/* GPL-only exported symbols. */
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const s32 *gpl_crcs;
bool using_gplonly_symbols;
这些字段用于 EXPORT_SYMBOL_GPL 导出的符号。只有声明为 GPL 许可证的模块才能使用这些符号。
2.3 生命周期函数字段¶
init / exit:初始化和清理函数¶
init:模块加载时调用的初始化函数(通过module_init()注册)exit:模块卸载时调用的清理函数(通过module_exit()注册)
重要:如果模块定义了 init 函数但没有 exit 函数,模块将无法被卸载(除非强制卸载)。
2.4 引用计数字段¶
refcnt:引用计数¶
源码位置:include/linux/module.h:582
引用计数跟踪有多少内核组件正在使用这个模块。当 refcnt > 0 时,模块无法被卸载。
关键函数:
/* 增加引用计数 */
int try_module_get(struct module *mod);
/* 减少引用计数 */
void module_put(struct module *mod);
/* 获取当前引用计数 */
int module_refcount(struct module *mod);
try_module_get 实现(kernel/module/main.c:1014):
/* third_party/linux-imx/kernel/module/main.c:1014 */
bool try_module_get(struct module *mod)
{
if (IS_ENABLED(CONFIG_MODULE_UNLOAD) && mod) {
preempt_disable();
/* 检查模块是否正在卸载 */
if (likely(atomic_read(&mod->refcnt) != MODULE_REF_BASE)) {
/* 增加引用计数 */
__module_get(mod);
preempt_enable();
return true;
}
preempt_enable();
return false;
}
return true;
}
EXPORT_SYMBOL(try_module_get);
2.5 sysfs 相关字段¶
mkobj:模块 kobject¶
struct module_kobject 定义(include/linux/module.h:45):
struct module_kobject {
struct kobject kobj; /* 内嵌的 kobject */
struct module *mod; /* 指向父模块 */
struct kobject *drivers_dir; /* 驱动目录 */
struct module_param_attrs *mp; /* 参数属性 */
struct completion *kobj_completion; /* 完成通知 */
} __randomize_layout;
kobject 是什么?
kobject 是内核设备模型的基础结构(定义在 include/linux/kobject.h:64):
/* third_party/linux-imx/include/linux/kobject.h:64 */
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
const struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
/* ... 状态标志 ... */
};
每个加载的模块都会在 /sys/module/ 下创建一个目录。
modinfo_attrs:模块信息属性¶
这是一个数组,包含模块的各种信息属性(如 license、author、description 等),这些信息会显示在 sysfs 中。
version / srcversion:版本信息¶
version:通过MODULE_VERSION()设置的模块版本字符串srcversion:源代码版本(基于源文件的 CRC 值)
holders_dir:持有者目录¶
在 sysfs 中,这个目录包含依赖当前模块的其他模块的符号链接。
2.6 段属性字段¶
sect_attrs:段属性¶
定义(kernel/module/sysfs.c:27):
/* third_party/linux-imx/kernel/module/sysfs.c:27 */
struct module_sect_attrs {
struct attribute_group grp;
unsigned int nsections;
struct module_sect_attrs attrs[];
};
这会在 /sys/module/<name>/sections/ 下创建各个段的信息文件,如 .text、.data、.bss 等。
notes_attrs:notes 属性¶
用于处理 ELF 的 SHT_NOTE 类型的段,通常包含构建信息。
2.7 内存管理字段¶
mem:模块内存区域¶
enum mod_mem_type(include/linux/module.h:332):
enum mod_mem_type {
MOD_TEXT = 0, /* 代码段 */
MOD_DATA, /* 数据段 */
MOD_RODATA, /* 只读数据 */
MOD_RO_AFTER_INIT, /* 初始化后变为只读 */
MOD_INIT_TEXT, /* 初始化代码 */
MOD_INIT_DATA, /* 初始化数据 */
MOD_INIT_RODATA, /* 初始化只读数据 */
MOD_MEM_NUM_TYPES,
MOD_INVALID = -1,
};
struct module_memory(include/linux/module.h:370):
struct module_memory {
void *base; /* 基地址 */
unsigned int size; /* 大小 */
#ifdef CONFIG_MODULES_TREE_LOOKUP
struct mod_tree_node mtn;
#endif
};
2.8 参数字段¶
kp / num_kp:内核参数¶
/* Kernel parameters. */
#ifdef CONFIG_SYSFS
struct mutex param_lock;
#endif
struct kernel_param *kp;
unsigned int num_kp;
这些字段描述模块的参数,通过 module_param() 宏定义的参数会存储在这里。
2.9 依赖关系字段¶
#ifdef CONFIG_MODULE_UNLOAD
/* What modules depend on me? */
struct list_head source_list;
/* What modules do I depend on? */
struct list_head target_list;
#endif
source_list:依赖当前模块的其他模块列表target_list:当前模块依赖的其他模块列表
struct module_use 定义(include/linux/module.h:314):
struct module_use {
struct list_head source_list;
struct list_head target_list;
struct module *source, *target;
};
2.10 其他重要字段¶
args:模块参数字符串¶
加载模块时传递的参数字符串。
taints:污点标志¶
记录模块导致内核污点的标志(如非 GPL 模块、强制加载等)。
percpu:Per-CPU 数据¶
模块的 Per-CPU 变量区域。
三、在内核代码中遍历所有已加载模块¶
这是一个常见的调试需求。下面是一个完整的示例代码,演示如何遍历所有模块并打印信息。
3.1 基本遍历方法¶
/*
* 遍历所有已加载模块并打印信息
*/
static void print_all_modules(void)
{
struct module *mod;
pr_info("=== All Loaded Modules ===\n");
/* 必须持有 module_mutex */
mutex_lock(&module_mutex);
list_for_each_entry(mod, &modules, list) {
pr_info("Module: %-20s State: %d Refcnt: %d\n",
mod->name,
mod->state,
module_refcount(mod));
}
mutex_unlock(&module_mutex);
}
3.2 查找特定模块¶
/*
* 根据名称查找模块
* 注意:调用者必须持有 module_mutex
*/
static struct module *find_module_by_name(const char *name)
{
struct module *mod;
list_for_each_entry(mod, &modules, list) {
if (strcmp(mod->name, name) == 0)
return mod;
}
return NULL;
}
/*
* 安全的模块查找接口
*/
static struct module *safe_find_module(const char *name)
{
struct module *mod;
mutex_lock(&module_mutex);
mod = find_module_by_name(name);
if (mod)
/* 增加引用计数,防止模块被卸载 */
if (!try_module_get(mod))
mod = NULL;
mutex_unlock(&module_mutex);
return mod;
}
3.3 打印模块详细信息¶
/*
* 打印模块的详细信息
*/
static void print_module_info(struct module *mod)
{
if (!mod) {
pr_info("Module is NULL\n");
return;
}
pr_info("=== Module Information ===\n");
pr_info("Name: %s\n", mod->name);
pr_info("State: %d\n", mod->state);
pr_info("RefCount: %d\n", module_refcount(mod));
if (mod->version)
pr_info("Version: %s\n", mod->version);
if (mod->srcversion)
pr_info("Source Version: %s\n", mod->srcversion);
pr_info("Exported Symbols: %u\n", mod->num_syms);
pr_info("GPL Symbols: %u\n", mod->num_gpl_syms);
#ifdef CONFIG_MODULE_UNLOAD
pr_info("Has exit function: %s\n", mod->exit ? "yes" : "no");
#endif
if (mod->num_kp > 0)
pr_info("Parameters: %u\n", mod->num_kp);
}
3.4 检查模块依赖关系¶
/*
* 打印模块依赖关系
*/
static void print_module_dependencies(struct module *mod)
{
struct module_use *use;
if (!mod)
return;
pr_info("=== %s Dependencies ===\n", mod->name);
mutex_lock(&module_mutex);
/* 打印我们依赖的模块 */
pr_info("Depends on:\n");
list_for_each_entry(use, &mod->target_list, target_list) {
pr_info(" - %s\n", use->target->name);
}
/* 打印依赖我们的模块 */
pr_info("Depended by:\n");
list_for_each_entry(use, &mod->source_list, source_list) {
pr_info(" - %s\n", use->source->name);
}
mutex_unlock(&module_mutex);
}
四、完整可编译代码示例¶
下面是一个完整的内核模块,演示如何遍历所有模块并打印信息。
4.1 模块源码(module_list.c)¶
// SPDX-License-Identifier: GPL-2.0
/*
* module_list.c - 列出所有已加载模块的示例
* 适用于 i.MX6ULL + Linux 6.12.49
*
* 编译方法:
* make -C <内核源码路径> M=$(pwd) modules
*
* 加载/卸载:
* insmod module_list.ko
* rmmod module_list
*
* 查看输出:
* dmesg | tail
*/
#include <linux/module.h> /* 模块核心API */
#include <linux/init.h> /* __init、__exit 宏 */
#include <linux/kernel.h> /* printk */
#include <linux/list.h> /* list_head */
#include <linux/mutex.h> /* mutex */
#include <linux/slab.h> /* kmalloc */
/* 外部声明:全局模块链表和互斥锁 */
extern struct list_head modules;
extern struct mutex module_mutex;
/*
* print_all_modules - 打印所有已加载模块的信息
*
* 注意:这个函数必须在持有 module_mutex 的情况下调用
*/
static void print_all_modules(void)
{
struct module *mod;
int count = 0;
pr_info("\n");
pr_info("========================================\n");
pr_info(" All Loaded Modules Information\n");
pr_info("========================================\n");
/* 遍历全局模块链表 */
list_for_each_entry(mod, &modules, list) {
count++;
pr_info("[%2d] %-20s State:%d Ref:%d\n",
count,
mod->name,
mod->state,
module_refcount(mod));
}
pr_info("Total modules: %d\n", count);
pr_info("========================================\n");
pr_info("\n");
}
/*
* print_gpl_modules - 打印使用 GPL 符号的模块
*/
static void print_gpl_modules(void)
{
struct module *mod;
int count = 0;
pr_info("\n");
pr_info("========================================\n");
pr_info(" Modules Using GPL Symbols\n");
pr_info("========================================\n");
list_for_each_entry(mod, &modules, list) {
if (mod->using_gplonly_symbols) {
count++;
pr_info("[%2d] %s (using %u GPL symbols)\n",
count,
mod->name,
mod->num_gpl_syms);
}
}
pr_info("Total GPL-using modules: %d\n", count);
pr_info("========================================\n");
pr_info("\n");
}
/*
* print_exporting_modules - 打印导出符号的模块
*/
static void print_exporting_modules(void)
{
struct module *mod;
int count = 0;
pr_info("\n");
pr_info("========================================\n");
pr_info(" Modules Exporting Symbols\n");
pr_info("========================================\n");
list_for_each_entry(mod, &modules, list) {
if (mod->num_syms > 0 || mod->num_gpl_syms > 0) {
count++;
pr_info("[%2d] %-20s Syms:%u GPL:%u\n",
count,
mod->name,
mod->num_syms,
mod->num_gpl_syms);
}
}
pr_info("Total exporting modules: %d\n", count);
pr_info("========================================\n");
pr_info("\n");
}
/*
* module_list_init - 模块初始化函数
*/
static int __init module_list_init(void)
{
pr_info("module_list: loading...\n");
/*
* 打印所有模块信息
* 注意:这里不需要手动获取 module_mutex,
* 因为在模块初始化期间,其他模块不会被卸载
*/
print_all_modules();
print_exporting_modules();
print_gpl_modules();
return 0;
}
/*
* module_list_exit - 模块清理函数
*/
static void __exit module_list_exit(void)
{
pr_info("module_list: unloading...\n");
pr_info("Goodbye!\n");
}
/*
* 模块元数据
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("IMX-Forge Tutorial <tutorial@example.com>");
MODULE_DESCRIPTION("List all loaded kernel modules");
MODULE_VERSION("1.0");
/*
* 注册模块的加载和卸载函数
*/
module_init(module_list_init);
module_exit(module_list_exit);
4.2 Makefile¶
# module_list 模块的 Makefile
# 内核源码路径 - 修改为你的实际路径
KERNEL_DIR := /home/charliechen/imx-forge/third_party/linux-imx
# 当前目录
PWD := $(shell pwd)
# 模块名称
obj-m := module_list.o
# 编译目标
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
# 清理目标
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
rm -f Module.symvers modules.order
# 安装目标
install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
.PHONY: all clean install
4.3 编译和测试¶
# 1. 进入模块目录
cd /path/to/module/directory
# 2. 编译模块
make
# 3. 加载模块
sudo insmod module_list.ko
# 4. 查看内核日志
dmesg | tail -50
# 5. 验证模块已加载
lsmod | grep module_list
# 6. 卸载模块
sudo rmmod module_list
# 7. 再次查看日志
dmesg | tail -10
五、常见错误、调试方法与内核报错解读¶
5.1 常见错误及解决方案¶
错误 1:Module is in use¶
原因:模块的引用计数不为 0,有其他内核组件正在使用它。
解决方法: 1. 找出谁在使用模块:
- 先卸载依赖的模块:
源码分析:这个检查发生在 delete_module() 中:
/* third_party/linux-imx/kernel/module/main.c:671 */
if (try_release_module_ref(mod) != 0) {
*forced = try_force_unload(flags);
if (!(*forced))
return -EWOULDBLOCK;
}
错误 2:Module has no exit function¶
原因:模块有 init 函数但没有 exit 函数。
解决方法:这通常是设计上的限制。如果必须卸载,只能使用强制卸载(不推荐)。
源码分析:
/* third_party/linux-imx/kernel/module/main.c:743 */
if (mod->init && !mod->exit) {
forced = try_force_unload(flags);
if (!forced) {
ret = -EBUSY;
goto out;
}
}
错误 3:Module state is not LIVE¶
原因:模块正处于加载或卸载过程中。
解决方法:等待模块操作完成,或检查是否有死锁。
源码分析:
/* third_party/linux-imx/kernel/module/main.c:735 */
if (mod->state != MODULE_STATE_LIVE) {
pr_debug("%s already dying\n", mod->name);
ret = -EBUSY;
goto out;
}
5.2 调试技巧¶
技巧 1:启用模块加载调试¶
# 启用模块相关的动态调试
echo 'file kernel/module/main.c +p' > /sys/kernel/debug/dynamic_debug/control
# 查看调试输出
dmesg -w
技巧 2:使用 ftrace 跟踪函数¶
# 启用模块加载函数跟踪
echo delete_module > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
# 查看跟踪结果
cat /sys/kernel/debug/tracing/trace
技巧 3:检查模块状态¶
# 查看模块详细信息
cat /sys/module/module_name/refcnt
cat /sys/module/module_name/sections/.text
# 查看模块参数
ls /sys/module/module_name/parameters/
# 查看 taint 信息
cat /proc/sys/kernel/tainted
5.3 内核 OOPS 解读¶
如果模块导致内核崩溃,OOPS 信息会包含模块相关的信息:
[ 123.456789] BUG: unable to handle page fault for address: 0xdeadbeef
[ 123.456790] Internal error: Oops: 86000007 [#1] PREEMPT SMP ARM
[ 123.456791] Modules linked in: my_module(O+) other_module ...
[ 123.456792] CPU: 0 PID: 1234 Comm: insmod Tainted: G O 6.12.49 ...
[ 123.456793] PC is at my_function+0x10/0x20 [my_module]
[ 123.456794] LR is at do_one_initcall+0x48/0x1b0
关键字段:
- Modules linked in: 列出所有加载的模块,O+ 表示正在加载
- Tainted: O 表示外部模块加载了
- PC is at: 崩溃发生的地址和函数
六、练习题与实战代码查看¶
练习 1:查找特定模块¶
题目:编写一个函数,根据模块名查找 struct module 指针。如果找到,返回模块指针并增加引用计数。
参考答案:
/*
* find_module_safe - 安全地查找模块
* @name: 模块名
*
* 返回:找到的模块指针,或 NULL
* 注意:调用者必须在完成后调用 module_put()
*/
static struct module *find_module_safe(const char *name)
{
struct module *mod;
mutex_lock(&module_mutex);
mod = find_module(name);
if (mod && !try_module_get(mod))
mod = NULL;
mutex_unlock(&module_mutex);
return mod;
}
/* 使用示例 */
void example_use(const char *name)
{
struct module *mod;
mod = find_module_safe(name);
if (mod) {
pr_info("Found module: %s\n", mod->name);
pr_info("RefCount: %d\n", module_refcount(mod));
module_put(mod); /* 释放引用 */
}
}
练习 2:统计模块数量¶
题目:编写一个函数,统计当前加载的模块数量,并分别统计有导出符号和使用 GPL 符号的模块数量。
参考答案:
/*
* struct module_stats - 模块统计信息
*/
struct module_stats {
unsigned int total; /* 总模块数 */
unsigned int exporting; /* 导出符号的模块数 */
unsigned int gpl_using; /* 使用 GPL 符号的模块数 */
unsigned int with_params; /* 有参数的模块数 */
};
/*
* count_modules - 统计模块信息
* @stats: 输出统计信息
*/
static void count_modules(struct module_stats *stats)
{
struct module *mod;
memset(stats, 0, sizeof(*stats));
mutex_lock(&module_mutex);
list_for_each_entry(mod, &modules, list) {
stats->total++;
if (mod->num_syms > 0 || mod->num_gpl_syms > 0)
stats->exporting++;
if (mod->using_gplonly_symbols)
stats->gpl_using++;
if (mod->num_kp > 0)
stats->with_params++;
}
mutex_unlock(&module_mutex);
}
/* 使用示例 */
static void print_module_stats(void)
{
struct module_stats stats;
count_modules(&stats);
pr_info("Module Statistics:\n");
pr_info(" Total modules: %u\n", stats.total);
pr_info(" Exporting symbols: %u\n", stats.exporting);
pr_info(" Using GPL symbols: %u\n", stats.gpl_using);
pr_info(" With parameters: %u\n", stats.with_params);
}
练习 3:分析模块依赖关系¶
题目:编写一个函数,打印指定模块的完整依赖树(包括直接依赖和间接依赖)。
参考答案:
#define MAX_DEPTH 10
/*
* print_dependencies_recursive - 递归打印依赖关系
* @mod: 当前模块
* @depth: 递归深度
* @visited: 已访问模块的 bitmap
*/
static void print_dependencies_recursive(struct module *mod,
int depth,
unsigned long *visited)
{
struct module_use *use;
int idx;
/* 检查是否已访问 */
idx = mod - list_entry(modules.next, struct module, list);
if (test_bit(idx, visited))
return;
set_bit(idx, visited);
/* 打印当前模块(带缩进) */
pr_info("%*s%s\n", depth * 2, "", mod->name);
if (depth >= MAX_DEPTH)
return;
/* 递归打印依赖的模块 */
list_for_each_entry(use, &mod->target_list, target_list) {
print_dependencies_recursive(use->target, depth + 1, visited);
}
}
/*
* print_module_dependency_tree - 打印模块依赖树
* @name: 模块名
*/
static void print_module_dependency_tree(const char *name)
{
struct module *mod;
unsigned long *visited;
size_t bitmap_size;
mutex_lock(&module_mutex);
mod = find_module(name);
if (!mod) {
pr_info("Module '%s' not found\n", name);
mutex_unlock(&module_mutex);
return;
}
/* 分配 visited bitmap */
bitmap_size = BITS_TO_LONGS(module_count) * sizeof(unsigned long);
visited = kzalloc(bitmap_size, GFP_KERNEL);
if (!visited) {
mutex_unlock(&module_mutex);
return;
}
pr_info("\n=== Dependency Tree for '%s' ===\n", name);
print_dependencies_recursive(mod, 0, visited);
pr_info("====================================\n\n");
kfree(visited);
mutex_unlock(&module_mutex);
}
练习 4:导出符号分析¶
题目:编写一个函数,分析指定模块导出了哪些符号,并打印符号名和值。
参考答案:
/*
* print_module_exports - 打印模块导出的符号
* @mod: 模块
*/
static void print_module_exports(struct module *mod)
{
unsigned int i;
if (mod->num_syms == 0 && mod->num_gpl_syms == 0) {
pr_info("Module '%s' exports no symbols\n", mod->name);
return;
}
pr_info("Module '%s' exported symbols:\n", mod->name);
/* 打印普通导出符号 */
for (i = 0; i < mod->num_syms; i++) {
const struct kernel_symbol *sym = &mod->syms[i];
pr_info(" [EXPORT] %p: %s\n",
(void *)sym->value, sym->name);
}
/* 打印 GPL 导出符号 */
for (i = 0; i < mod->num_gpl_syms; i++) {
const struct kernel_symbol *sym = &mod->gpl_syms[i];
pr_info(" [GPL] %p: %s\n",
(void *)sym->value, sym->name);
}
}
练习 5:模块状态监控¶
题目:创建一个内核线程,定期监控模块状态变化,并在状态改变时打印日志。
参考答案:
#include <linux/kthread.h>
#include <linux/delay.h>
static struct task_struct *monitor_thread;
static bool monitor_running = true;
/*
* module_monitor_thread - 模块监控线程
* @data: 未使用
*/
static int module_monitor_thread(void *data)
{
struct module *mod;
int last_count = 0;
while (!kthread_should_stop()) {
int current_count = 0;
mutex_lock(&module_mutex);
list_for_each_entry(mod, &modules, list) {
current_count++;
}
mutex_unlock(&module_mutex);
/* 检测模块数量变化 */
if (current_count != last_count) {
pr_info("Module count changed: %d -> %d\n",
last_count, current_count);
last_count = current_count;
}
/* 每 5 秒检查一次 */
msleep(5000);
}
return 0;
}
/*
* start_monitor - 启动监控线程
*/
static int start_monitor(void)
{
monitor_thread = kthread_run(module_monitor_thread,
NULL, "module_monitor");
if (IS_ERR(monitor_thread))
return PTR_ERR(monitor_thread);
pr_info("Module monitor thread started\n");
return 0;
}
/*
* stop_monitor - 停止监控线程
*/
static void stop_monitor(void)
{
if (monitor_thread) {
kthread_stop(monitor_thread);
monitor_thread = NULL;
pr_info("Module monitor thread stopped\n");
}
}
/* 在模块初始化时启动,退出时停止 */
static int __init my_module_init(void)
{
return start_monitor();
}
static void __exit my_module_exit(void)
{
stop_monitor();
}
七、实战代码查看¶
为了更深入地理解 struct module,建议查看以下内核源码文件:
7.1 核心头文件¶
| 文件 | 说明 |
|---|---|
include/linux/module.h |
struct module 定义和模块 API |
include/linux/moduleparam.h |
模块参数相关定义 |
include/linux/export.h |
符号导出宏定义 |
include/linux/kobject.h |
kobject 定义(sysfs 基础) |
7.2 核心实现文件¶
| 文件 | 说明 |
|---|---|
kernel/module/main.c |
模块加载/卸载核心实现 |
kernel/module/sysfs.c |
模块 sysfs 接口实现 |
kernel/module/version.c |
模块版本检查 |
kernel/module/kallsyms.c |
符号表处理 |
7.3 关键函数位置¶
| 函数 | 文件位置 | 说明 |
|---|---|---|
SYSCALL_DEFINE2(delete_module) |
kernel/module/main.c:700 |
卸载模块系统调用 |
try_stop_module() |
kernel/module/main.c:668 |
停止模块 |
free_module() |
kernel/module/main.c:1261 |
释放模块 |
try_module_get() |
kernel/module/main.c:1014 |
增加引用计数 |
module_put() |
kernel/module/main.c:1028 |
减少引用计数 |
find_module() |
kernel/module/main.c:647 |
查找模块 |
mod_sysfs_setup() |
kernel/module/sysfs.c:371 |
设置 sysfs |
7.4 内核文档¶
Documentation/kbuild/modules.rst- 外部模块构建指南Documentation/driver-api/- 驱动开发 API 文档Documentation/core-api/kobject.rst- kobject 和 sysfs
八、下一章预告¶
到这里,你应该对 struct module 有了全面的理解。你知道了模块的完整生命周期、各个字段的含义,以及如何在内核中遍历和操作模块。
下一章,我们将探索更高级的主题:
- 模块间的符号依赖和通信
- 模块签名和安全机制
- 设备模型与模块的关系
- 实战:编写一个完整的字符设备驱动
准备好了吗?让我们继续深入内核模块开发的世界。
参考资料¶
内核源码引用¶
本文档所有源码引用基于 linux-imx 6.12.49,位于 third_party/linux-imx/ 目录:
include/linux/module.h:410- struct module 定义include/linux/module.h:320- enum module_statekernel/module/main.c:700- delete_module 系统调用kernel/module/main.c:668- try_stop_module 函数kernel/module/main.c:1261- free_module 函数kernel/module/sysfs.c:371- mod_sysfs_setup 函数
延伸阅读¶
作者: IMX-Forge 项目组 最后更新: 2026年3月 内核版本: 6.12.49 (linux-imx)