龙空技术网

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

一起学嵌入式 1478

前言:

眼前兄弟们对“linux卸载内核模块命令”大致比较关怀,你们都想要分析一些“linux卸载内核模块命令”的相关文章。那么小编也在网上网罗了一些对于“linux卸载内核模块命令””的相关文章,希望兄弟们能喜欢,兄弟们快快来学习一下吧!

开篇

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

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 文件是基于某一特定内核源码树所构成的,版本不一致可能会引起潜在的问题。

标签: #linux卸载内核模块命令 #linux卸载n卡驱动