linux 内核模块 强制卸载
0 问题:某项目中,关于一种调用设备驱动程序,出现异常时,驱动设备无法正常退出(lsmod 显示驱动设备被占用、无法rmmod 退出),也无法继续使用的问题。
1 linux 模块
内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。模块具有以下特点:
模块本身不被编译入内核映像,从而控制了内核的大小 。
模块一旦被加载,它就和内核中的其他部分一样 。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
2 编译内核模块
hello 模块
#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h> // init&exit相关宏
#include <linux/kernel.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
MODULE_DESCRIPTION("hello world module");static int __init hello_init(void)
{printk(KERN_WARNING "hello world.\n");return 0;
}
static void __exit hello_exit(void)
{printk(KERN_WARNING "hello exit!\n");
}module_init(hello_init);
module_exit(hello_exit);
Makefile
ifneq ($(KERNELRELEASE),)
obj-m :=hello.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
all:make -C $(KDIR) M=$(PWD) modules
clean:rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
endif
使用
make
sudo insmod hello.ko
sudo rmmod hello
dmesg
[10611.395806] hello world.
[10644.832158] hello exit!
KERNELRELEASE
这个 makefile 在一次典型的建立中要被读 2 次。
KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次(刚开始)读取执行此Makefile时,KERNELRELEASE没有被定义(为空),所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C(KDIR)指明跳转到内核源码目录下读取那里的Makefile,M=(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容(指 obj-m :=hello.o)。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
以上介绍了内核模块的概念以及内核模块的编译。
3 强制卸载内核模块
发现一篇非常好的文章,对照无法强制卸载内核模块的各个情况,相应进行了介绍。
原文链接:Linux强制卸载内核模块(由于驱动异常导致rmmod不能卸载)
对照文章,本人遇到的驱动被占用问题属于以下这种类型。
引用计数在有其它模块或者内核本身引用的时候不为0,要卸载就要等待它们不引用为止 | used |
需要通过外部修正的方式, 将驱动的引用计数置 0 即可
|
(1)现象
rmmod: ERROR: Module XXX is in use
(2)其本质就是模块的模块的引用计数不为 0
, 要解决此类问题, 只需要将模块的引用计数强制置为 0
即可.
查找到
none_exit
模块的内核模块结构struct moudle
, 可以通过find_module
函数查找到, 也可以参照find_module
函数实现.重置模块的引用计数
// 清除驱动的引用计数 for_each_possible_cpu(cpu) {local_set((local_t*)per_cpu_ptr(&(mod->refcnt), cpu), 0);//local_set(__module_ref_addr(mod, cpu), 0);//per_cpu_ptr(mod->refptr, cpu)->decs;//module_put(mod); } atomic_set(&mod->refcnt, 1);
4 强制卸载驱动源码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cpumask.h>
#include <linux/list.h>
#include <asm-generic/local.h>
#include <linux/platform_device.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>/** 加载模块的时候, 传递字符串到模块的一个全局字符数组里面** module_param_string(name, string, len, perm)** @name 在加载模块时,参数的名字* @string 模块内部的字符数组的名字* @len 模块内部的字符数组的大小* #perm 访问权限** */
static char *modname = NULL;
module_param(modname, charp, 0644);
MODULE_PARM_DESC(modname, "The name of module you want do clean or delete...\n");//#define CONFIG_REPLACE_EXIT_FUNCTION#ifdef CONFIG_REPLACE_EXIT_FUNCTION
// 此处为外部注册的待卸载模块的exit函数
// 用于替代模块原来的exit函数
// 注意--此函数由于需要被待删除模块引用, 因此不能声明为static
/* static */ void force_replace_exit_module_function(void)
{/// 此处完善待卸载驱动的 exit/cleanup 函数/printk("module %s exit SUCCESS...\n", modname);
// platform_device_unregister((struct platform_device*)(*(int*)symbol_addr));
}
#endif // CONFIG_REPLACE_EXIT_FUNCTIONstatic int force_cleanup_module(char *del_mod_name)
{struct module *mod = NULL, *relate = NULL;int cpu;
#ifdef CONFIG_REPLACE_EXIT_FUNCTIONvoid *origin_exit_addr = NULL;
#endif/// 找到待删除模块的内核module信息/
#if 0// 方法一, 遍历内核模块树list_mod查询struct module *list_mod = NULL;/* 遍历模块列表, 查找 del_mod_name 模块 */list_for_each_entry(list_mod, THIS_MODULE->list.prev, list){if (strcmp(list_mod->name, del_mod_name) == 0){mod = list_mod;}}/* 如果未找到 del_mod_name 则直接退出 */if(mod == NULL){printk("[%s] module %s not found\n", THIS_MODULE->name, modname);return -1;}
#endif// 方法二, 通过find_mod函数查找if((mod = find_module(del_mod_name)) == NULL){printk("[%s] module %s not found\n", THIS_MODULE->name, del_mod_name);return -1;}else{printk("[before] name:%s, state:%d, refcnt:%u\n",mod->name ,mod->state, module_refcount(mod));}/// 如果有其他驱动依赖于当前驱动, 则不能强制卸载, 立刻退出//* 如果有其他模块依赖于 del_mod */if (!list_empty(&mod->source_list)){/* 打印出所有依赖target的模块名 */list_for_each_entry(relate, &mod->source_list, source_list){printk("[relate]:%s\n", relate->name);}}else{printk("No modules depond on %s...\n", del_mod_name);}/// 清除驱动的状态和引用计数/// 修正驱动的状态为LIVEmod->state = MODULE_STATE_LIVE;// 清除驱动的引用计数for_each_possible_cpu(cpu){local_set((local_t*)per_cpu_ptr(&(mod->refcnt), cpu), 0);//local_set(__module_ref_addr(mod, cpu), 0);//per_cpu_ptr(mod->refptr, cpu)->decs;//module_put(mod);}atomic_set(&mod->refcnt, 1);#ifdef CONFIG_REPLACE_EXIT_FUNCTION/// 重新注册驱动的exit函数/origin_exit_addr = mod->exit;if (origin_exit_addr == NULL){printk("module %s don't have exit function...\n", mod->name);}else{printk("module %s exit function address %p\n", mod->name, origin_exit_addr);}mod->exit = force_replace_exit_module_function;printk("replace module %s exit function address (%p -=> %p)\n", mod->name, origin_exit_addr, mod->exit);
#endifprintk("[after] name:%s, state:%d, refcnt:%u\n",mod->name, mod->state, module_refcount(mod));return 0;
}static int __init force_rmmod_init(void)
{return force_cleanup_module(modname);
}static void __exit force_rmmod_exit(void)
{printk("=======name : %s, state : %d EXIT=======\n", THIS_MODULE->name, THIS_MODULE->state);
}module_init(force_rmmod_init);
module_exit(force_rmmod_exit);MODULE_LICENSE("GPL");
5 Makefile
MODULE_NAME := force_rmmod
#MODCFLAGS:=-O2 -Wall -DMODULE -D__KERNEL__ -DLINUX -std=c99
#EXTRA_CFLAGS += $(MODULE_FLAGS) $(CFG_INC) $(CFG_INC)
EXTRA_CFLAGS += -g -std=gnu99 -Wfatal-errors ifneq ($(KERNELRELEASE),) # kernelspaceobj-m := $(MODULE_NAME).oelse # userspaceCURRENT_PATH ?= $(shell pwd)
LINUX_KERNEL ?= $(shell uname -r)
LINUX_KERNEL_PATH ?= /lib/modules/$(LINUX_KERNEL)/buildCURRENT_PATH := $(shell pwd)modules:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesmodules_install:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules_installclean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) cleanrm -f modules.order Module.symvers Module.markers.PHNOY:modules modules_install cleanendif
6 使用方法
# 通过 `modname` 制定待卸载驱动的信息
sudo insmod force_rmmod.ko modname=卸载驱动名称
sudo rmmod 卸载驱动名称
linux 内核模块 强制卸载相关推荐
- 一文了解linux 内核模块 强制卸载
[推荐阅读] 需要多久才能看完linux内核源码? 概述Linux内核驱动之GPIO子系统API接口 一篇长文叙述Linux内核虚拟地址空间的基本概括 0 问题:某项目中,关于一种调用设备驱动程序,出 ...
- 在 Linux 中强制卸载的 3 种方法显示“设备正忙”
在某些情况下,当您尝试卸载文件系统尤其是 NFS 时,它会显示"设备正忙"消息.当 NFS 服务器有一些问题(主要是无法访问)并且您有一个软 NFS 挂载时,通常会发生这种情况. ...
- Linux下强制卸载已安装的php!
原先在centOS6.6上已经安装好了Lamp环境,因为php5.3版本比较低,就使用了remi源升级到5.5,然后问题就来了:项目中使用的php-xml在centOS下找不到yum安装包,而且其他的 ...
- linux怎么强制解挂,linux下强制卸载挂接点——umount+Fuser命令详解
fuser -km /soft umount /soft Linux命令Fuser详解 使用文件或者套节字来表示识别进程.我常用的他的两个功能:查看我需要的进程和我要杀死我查到的进程 比如当你想umo ...
- linux下强制卸载命令,强制删除rpm包的方法
删除软件 要删除软件非常简单,只要执行下面的命令就行: # rpm –e xanim 这时,用户要注意使用的是软件的名称xanim,而不是软件包的名称xanim-27.64-3.i386.rpm. 如 ...
- c需要实现安装卸载Linux模块,Linux内核模块编译与加载
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用所需要的组件? 方法一: 把所有的组件都编译进内核文件,即Zlmage或bzlmage,但会导致两个问题:一是生成的内核文件过大:二是如 ...
- 强制卸载linux 磁盘
转载来自http://www.xitongzhijia.net/xtjc/20150610/50557.html 对于Linux系统下挂载的文件系统,通常情况下是无法卸载的,可通过命令进行强行卸载,下 ...
- Linux 强制卸载挂载点---fuser 命令详解
Linux fuser 命令详解 强制卸载挂载点 如果你的光盘挂载在/mnt/cdrom 使用umount /dev/cdrom提示busy 可以使用以下命令强行卸载. # fuser -km /mn ...
- linux强制卸载_如何强制Linux卸载文件系统?
linux强制卸载 Some time, Linux fails to unmount a filesystem reporting "device is busy". I und ...
最新文章
- 计算机编码技术ppt,计算机编码技术.ppt
- MongoDB中_id(ObjectId)生成
- 王道计算机考研 数据结构 (树与二叉树)
- ITK:处理矢量图像的N个分量
- 同源策略_如何支持跨域
- 华为P50系列已适配鸿蒙OS 2.0 Beta2:有望4月亮相
- FD.io VPP:探究分段场景下vlib_buf在收发包的处理(dpdk_plugin.so)、rte_mbuf与vlib_buf 关系
- 扩展类载入器的载入问题
- Android开发笔记(一百四十)Word文件的读取与显示
- 《精通Spring4.X企业应用开发实战》读后感第六章(国际化)
- centos7安装uwsgi报错_nginx + uwsgi 发布django项目!(linux为centos7)
- 漏洞挖掘分析技术总结
- Azure DevOps Server(TFS) 客户端分析
- 1、Linux下搭建研究live555的开发环境
- 我屮艸芔茻!!!什么情况,就刚才,百年之功废于一旦!!博客园,你TM想吃翔了吧!...
- 微信小程序-获取用户头像信息以及修改用户头像
- 基于TensorFlow的Cats vs. Dogs(猫狗大战)实现和详解(1)
- Fiddler抓包解析https且配置安卓手机CA证书、抓取微信数据包
- windows 10 Quick Assist 远程协助工具
- 这本《零信任网络》上架两个月,一直是程序员们最青睐的网络安全书