(5)Linux内核模块卸载过程(基于内核源码的解析)
内核模块的卸载是加载的逆过程,核心目标是安全回收模块占用的资源、解除内核依赖,并将模块从内核中移除。卸载过程需严格校验模块的 “可卸载性”(无依赖、无资源占用),再执行资源清理与内存释放,以下结合内核源码(5.x/6.x 版本)拆解完整流程。
一、卸载流程总览
卸载操作由用户空间命令(rmmod/modprobe -r)触发,内核通过 sys_delete_module 系统调用完成核心逻辑,流程需确保 “无引用、无依赖、资源全回收”,具体链路:
用户空间(rmmod)→ 调用 sys_delete_module() → 内核空间处理:
1. 权限/状态校验(模块是否可卸载)
2. 检查引用计数(是否被其他模块/进程占用)
3. 执行模块卸载函数(module_exit 注册的逻辑)
4. 解除符号依赖(从全局符号表移除导出符号)
5. 回收内存资源(core 段、模块控制结构体)
6. 从内核模块列表移除 → 卸载完成
二、核心环节拆解(结合内核源码)
1. 用户空间触发:rmmod 的 “前奏操作”
卸载的入口是用户空间的 rmmod 命令(或 modprobe -r),其核心工作:
- 解析目标模块名,在
/sys/module/目录(内核暴露的模块信息接口)中确认模块状态; - 调用
sys_delete_module系统调用,传递模块名及卸载标志(如是否强制卸载); - 注:
modprobe -r会先检查依赖,若目标模块是其他模块的依赖,会先卸载依赖它的模块(需无其他占用),再卸载目标模块。
2. 内核空间核心入口:sys_delete_module 系统调用
卸载的内核入口是 SYSCALL_DEFINE2(delete_module, ...),它是卸载流程的 “总控制”,简化源码如下:
#include <linux/module.h>
#include <linux/syscalls.h>
SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
unsigned int, flags) {
struct module *mod;
char name[MODULE_NAME_LEN];
int err;
// 1. 权限校验:仅 root 或拥有 CAP_SYS_MODULE 权限的用户可卸载
err = may_delete_module();
if (err)
return err;
// 2. 从用户空间拷贝模块名到内核
if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
return -EFAULT;
name[MODULE_NAME_LEN-1] = '\0';
// 3. 查找目标模块(从内核全局模块列表中匹配模块名)
mod = find_module(name);
if (!mod)
return -ENOENT;
// 4. 核心卸载逻辑:检查可卸载性 + 执行卸载操作
err = do_unload_module(mod, flags);
if (err)
return err;
return 0;
}
3. 核心子流程:do_unload_module —— 卸载的 “安全闸门”
do_unload_module 是卸载的核心函数,负责校验模块是否可卸载,并触发后续资源回收,简化源码如下:
static int do_unload_module(struct module *mod, unsigned int flags) {
int err;
// 步骤1:检查模块状态(仅“活动态”MODULE_STATE_LIVE 可卸载)
if (mod->state != MODULE_STATE_LIVE) {
pr_err("模块 %s 处于不可卸载状态(当前状态:%d)\n", mod->name, mod->state);
return -EBUSY;
}
// 步骤2:检查引用计数(是否被其他模块/进程占用)
if (!try_module_get(mod)) {
pr_err("模块 %s 引用计数非零(当前计数:%d)\n", mod->name, module_refcount(mod));
return -EBUSY;
}
// 步骤3:检查依赖(是否有其他模块依赖当前模块)
if (!list_empty(&mod->source_list)) {
pr_err("模块 %s 被其他模块依赖,无法卸载\n", mod->name);
module_put(mod);
return -EWOULDBLOCK;
}
// 步骤4:执行模块卸载函数(module_exit 注册的逻辑)
err = do_exit_module(mod);
if (err) {
module_put(mod);
return err;
}
// 步骤5:回收模块资源 + 从内核移除
free_module(mod);
return 0;
}
4. 关键子环节拆解
(1)状态与引用计数校验:卸载的 “安全前提”
- 状态校验:模块状态必须是
MODULE_STATE_LIVE(活动态),若处于加载中(MODULE_STATE_COMING)或卸载中(MODULE_STATE_GOING),直接拒绝卸载; - 引用计数校验:内核为每个模块维护
kref类型的引用计数器(mod->refcnt),若计数 > 0,说明模块被其他模块 / 进程占用(如进程打开了模块注册的设备文件),无法卸载; - 依赖校验:模块的
source_list链表记录了 “依赖当前模块的其他模块”,若链表非空,说明当前模块是其他模块的依赖,必须先卸载依赖它的模块,否则拒绝卸载。
(2)执行卸载函数:do_exit_module —— 资源清理的核心
do_exit_module 会调用模块通过 module_exit(exit_func) 注册的卸载函数,这是模块自定义资源回收的入口,逻辑如下:
static int do_exit_module(struct module *mod) {
// 1. 将模块状态置为“卸载中”(MODULE_STATE_GOING),防止并发操作
mod->state = MODULE_STATE_GOING;
// 2. 调用模块的卸载函数(module_exit 注册的函数)
if (mod->exit != NULL) {
mod->exit(); // 执行自定义卸载逻辑:注销设备、释放中断、删除/proc节点等
}
// 3. 清理内核自动管理的资源:
// - 移除模块导出的符号(从全局符号表 kallsyms 中删除)
module_unexport_symbols(mod);
// - 清理模块参数、sysfs 节点等
module_cleanup_params(mod);
return 0;
}
模块卸载函数(exit_func)的典型工作:
- 注销设备驱动(如
unregister_chrdev); - 释放申请的中断(
free_irq); - 删除
/proc//sys中创建的节点; - 释放模块加载时申请的内存(如
kfree/vfree); - 解除与其他内核子系统的绑定(如从文件系统链表中移除)。
(3)资源回收:free_module —— 彻底移除模块
free_module 是卸载的最后一步,负责回收模块占用的所有内核资源,核心逻辑:
static void free_module(struct module *mod) {
// 1. 释放模块的 core 段内存(加载时通过 vmalloc 分配的核心内存)
vfree(mod->module_core);
// 2. 释放模块控制结构体(struct module)本身的内存
kfree(mod);
// 3. 从内核全局模块列表(modules 链表)中移除该模块
list_del(&mod->list);
// 4. 更新内核模块统计信息
module_update_counts();
}
这里的 mod->module_core 指向加载时为 core 段分配的虚拟连续内存(包含 .text、.data 等核心节区),通过 vfree 释放(vmalloc 的对应释放函数);而 struct module 结构体是通过 kmalloc 分配的,因此用 kfree 释放。
三、卸载流程的关键约束
- 原子性与安全性:卸载过程中任何一步失败(如引用计数非零、依赖未解除),都会终止流程并保留模块状态,避免内核资源泄漏;
- init 段无需单独释放:加载时的 init 段在初始化完成后已被释放,卸载仅需处理 core 段;
- 强制卸载的风险:
rmmod -f可跳过引用计数校验强制卸载,但可能导致内核崩溃(如模块资源被占用时强行释放),仅用于调试场景。
四、卸载流程总结
内核模块卸载是 “校验 - 清理 - 回收” 的闭环:
4. 校验层:确保模块无依赖、无引用、状态合法;
5. 清理层:执行模块自定义卸载逻辑 + 内核自动资源清理;
6. 回收层:释放内存、移除模块控制结构,彻底脱离内核管理。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)