win7快速卸载驱动 (win7 卸载驱动) 开篇

上一篇文章分析了内核模块的加载过程:

Linux设备驱动-模块加载过程

这篇文章来讲讲内核模块的卸载过程机制。

本文引用的内核代码参考来自版本 linux-5.15.4 。

在用户空间,通过指令 rmmod 可以将一个内核模块从系统中卸载,使用方法如下:

rmmod xx /* 卸载已经加载的内核模块 xx */

注意,卸载内核模块需要具有 CAP_SYS_MODULE 权限(root用户或者其他具有这个权限的用户),否则会加载失败。

rmmod 指令通过系统调用 sys_module_module() 完成卸载工作。

系统调用 sys_delete_module

sys_delete_module() 函数原型如下:

long sys_delete_module(const char __user *name_user, unsigned int flags);

参数 name_user 是模块名称。参数 flags 为卸载标志。

函数的具体代码如下(已经将函数名称替换为实际展开后的形式),关键函数添加了注释:

/* <kernel/module.c> */long sys_delete_module(const char __user *name_user, unsigned int 电脑 flags){ struct module *mod; char name[MODULE_NAME_LEN]; int ret, forced = 0; /* 判断是否有卸载模块的权限 */ if (!capable(CAP_SYS_MODULE) || modules_disabled) return -EPERM; /* 从用户空间复制模块名称到内核空间 */ if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0) return -EFAULT; name[MODULE_NAME_LEN-1] = '\0'; audit_log_kern_module(name); /* 互斥锁 */ if (mutex_lock_interruptible(&module_mutex) != 0) return -EINTR; /* 在内核链表中查找要卸载的内核模块 */ mod = find_module(name); if (!mod) { ret = -ENOENT; goto out; } /* 检查模块的依赖关系 */ if (!list_empty(&mod->source_list)) { /* Other modules depend on us: get rid of them first. */ ret = -EWOULDBLOCK; goto out; } /* 判断模块是否已经加载成功 */ 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; } /* If it has an init func, it must have an exit func to unload */ if (mod->init && !mod->exit) { forced = try_force_unload(flags); if (!forced) { /* This module can't be removed */ ret = -EBUSY; goto out; } } /* Stop the machine so refcounts can't move and disable module. */ ret = try_stop_module(mod, flags, &forced); if (ret != 0) goto out; 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(); /* 记录最近卸载的模块的名字,便于诊断问题 */ strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); /* 释放模块占用的资源 */ 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;}

下边结合代码来分析模块的卸载过程。

模块的卸载过程

模块卸载的过程由一系列执行动作组合而成,此处只介绍其中关键的执行步骤。其他的函数调用,有兴趣的话可以自己研究一下。

判断是否可以卸载模块

通过以下代码

if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0) return -EFAULT;

判断用户是否具备卸载模块的权限,或者内核是否允许卸载模块。

如果不能卸载此内核模块,则退出并返回错误码 -EFAULT。否则,继续执行卸载动作。

查找模块

首先,将要卸载的模块名称从用户空间复制到内核空间,调用函数 strncpy_from_user():

if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0) return -EFAULT;

然后,通过函数 find_module() 在内核模块链表 modules 中查找要卸载的模块,函数的入参为模块的名字。其函数的实现代码如下:

/* <kernel/module.c> */struct module *find_module(const char *name){ return find_module_all(name, strlen(name), false);}/* 具体的执行函数 */static struct module *find_module_all(const char *name, size_t len, bool even_unformed){ struct module *mod; module_assert_mutex_or_preempt(); /* 遍历内核模块链表 */ list_for_each_entry_rcu(mod, &modules, list, lockdep_is_held(&module_mutex)) { if (!even_unformed && mod->state == MODULE_STATE_UNFORMED) continue; if (strlen(mod->name) == len && !memcmp(mod->name, name, len)) return mod; } return NULL;}

由代码可知,通过 list_for_each_entry_rcu() 实现遍历 modules 链表中每一个模块。如果查找到指定的模块,那么 find_module() 返回该模块的 mod 结构指针,否则返回 NULL。

检查模块依赖关系

为了系统的稳定,一个有依赖关系的模块不应该从系统中卸载,即有其他模块依赖将要删除的模块。模块间的依赖关系通过结构体 struct module 中的成员变量 source_list 和 target_list 来实现。

其中,source_list 用于将对此模块有依赖的模块链接起来。因此,检查卸载模块的 source_list 链表是否为空,即可判断模块是否被其他模块依赖,相关代码段为:

if (!list_empty(&mod->source_list)) { /* Other modules depend on us: get rid of them first. */ ret = -EWOULDBLOCK; goto out; }

如果存在依赖关系,则结束卸载,并返回错误码。否则,继续执行卸载动作。

释放模块占用的资源

如果前边步骤一切正常,sys_delete_module() 会调用 free_module() 函数来做模块卸载末期的清理工作。包括:更新模块状态为 MODULE_STATE_UNFORMED,将卸载的模块从 modules 链表中移除,将模块占用的 CORE section 空间释放,释放模块接收的参数所占空间等。

小结

在此,对内核模块的基本内容进行简单的总结:

内核模块可以在系统运行期间动态加载。用户空间,加载和卸载模块使用 mod utils 的工具包,包括最基本的 insmod 和 rmmod 工具。内核模块的文件格式是一种可重定位的 ELF 文件。模块可以调用内核源码或者其他模块实现的函数。系统中所有加载成功的模块都被链接到 modules 链表中。一个 .ko 文件是基于某一特定内核源码树所构成的,版本不一致可能会引起潜在的问题。