红联Linux门户
Linux帮助

linux3.x内核如何强制卸载模块?

发布时间:2016-07-20 10:17:57来源:linux网站作者:xumin330774233
一、问题现象:
在insmod时调用的init函数代码执行过程中出现oops,导致rmmod卸载失败,此时不得不重启目标板?
 
二、下面是《精通linux设备驱动程序开发》中模拟鼠标的输入设备驱动的内核模块vms.c代码:
#include <linux/fs.h>  
#include <asm/uaccess.h>  
#include <linux/input.h>  
#include <linux/platform_device.h>  
#include <linux/pci.h>  
#include <linux/module.h>   
static struct platform_device *vms_dev;  
static struct input_dev *vms_input_dev;  
static ssize_t write_vms(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count)  
{  
int x, y;  
sscanf(buffer, "%d%d", &x, &y);  
printk("(x,y)=(%d,%d)\n", x, y);  
input_report_rel(vms_input_dev, REL_X, x);  
input_report_rel(vms_input_dev, REL_Y, y);  
input_sync(vms_input_dev);  
return count;  
}  
DEVICE_ATTR(coordinates, 0644, NULL, write_vms);  
static struct attribute *vms_attrs[] = {  
&dev_attr_coordinates.attr,  
NULL  
};  
static struct attribute_group vms_attr_group = {  
.attrs = vms_attrs,  
};  
static int __init vms_init(void)  
{  
int err;  
printk("vms_init===========\n");  
vms_dev = platform_device_register_simple("vms", -1, NULL, 0);   
if (IS_ERR(vms_dev)) {  
printk("############################platform_device_register_simple failed\n");  
return PTR_ERR(vms_dev);  
}  
printk("vms_dev = 0x%x\n", vms_dev);  
err = sysfs_create_group(&vms_dev->dev.kobj, &vms_attr_group);  
if (err) {  
printk("==============================sysfs_create_group failed\n");  
return -1;  
}  
printk("vms_init++++++++++\n");  
vms_input_dev = input_allocate_device();  
if (!vms_input_dev) {  
printk("Bad input_allocate_device()\n");  
return -1;  
}
//vms_input_dev->name = "Vms_device";  
strcpy(vms_input_dev->name, "Vms test"); // oops!,程序退出  
set_bit(EV_REL, vms_input_dev->evbit);  
set_bit(REL_X, vms_input_dev->relbit);  
set_bit(REL_Y, vms_input_dev->relbit);  
input_register_device(vms_input_dev);   
//strcpy(vms_input_dev->name, "Vms test");  
printk("Virtual Mouse Driver Initialized.\n");  
return 0;  
}  
static void __exit vms_cleanup(void)  
{  
input_unregister_device(vms_input_dev);   
sysfs_remove_group(&vms_dev->dev.kobj, &vms_attr_group);  
platform_device_unregister(vms_dev);  
printk("Virtual Mouse Driver Exited.\n");  
return;  
}  
module_init(vms_init);  
module_exit(vms_cleanup);  
MODULE_LICENSE("Dual BSD/GPL");  
MODULE_AUTHOR("Xumin");  
insmod ./vms.ko后通过dmesg看到的信息:
linux3.x内核如何强制卸载模块?
然后sudo rmmod vms模块,会发现卸载不了:
linux3.x内核如何强制卸载模块?
我们知道,rmmod是调用sys_delete_module函数进行删除模块的,下面是其具体实现的的解析:
linux3.x内核如何强制卸载模块?
所以需要通过编写专门用于卸载vms内核模块的内核模块force_rmmod,下面是force_rmmod.c的源代码:
#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>  
static void force(void)  
{  
int symbol_addr;  
printk("XXXXXX, force!\n");  
symbol_addr = kallsyms_lookup_name("vms_dev");  
platform_device_unregister((struct platform_device*)(*(int*)symbol_addr));  
}  
static int __init force_rmmod_init(void)  
{  
struct module *mod, *relate;  
int cpu;  
int symbol_addr;  
symbol_addr = kallsyms_lookup_name("vms_dev");  
printk("YYYYY, symbol_addr:0x%x\n", symbol_addr);  
printk("[module init] name:%s, state:%d\n", THIS_MODULE->name, THIS_MODULE->state);  
list_for_each_entry(mod, THIS_MODULE->list.prev, list) 
{  
if (strcmp(mod->name, "vms") == 0) {  
printk("[vms]:name:%s, state:%d, refcnt:%u\n",  
mod->name ,mod->state, module_refcount(mod));  
if (!list_empty(&mod->source_list)) {  
list_for_each_entry(relate, &mod->source_list, source_list)
printk("[relate]:%s\n", relate->name);  
} else {  
printk("used by NULL...\n");  
}  
mod->state = 0;  
mod->exit = force;  
for_each_possible_cpu(cpu)  
local_set((local_t*)per_cpu_ptr(mod->refptr, cpu), 0);  
//local_set(__module_ref_addr(mod, cpu), 0);  
//per_cpu_ptr(mod->refptr, cpu)->decs;  
//module_put(mod);  
printk("[after]:name:%s, state:%d, refcnt:%u\n",  
mod->name, mod->state, module_refcount(mod));   
}  
}  
return 0;  
}
static void __exit force_rmmod_exit(void)  
{  
printk("[module exit] name:%s, state:%d\n", THIS_MODULE->name, THIS_MODULE->state);  
}  
module_init(force_rmmod_init);  
module_exit(force_rmmod_exit);  
MODULE_LICENSE("GPL");
通过安装force_rmmod.ko后,会发现vms模块目前的引用计数为1,且状态处于1,通过上面对sys_delete_module函数的理解得知,删除一个模块,需要将模块状态置为0,且引用计数置为0。
linux3.x内核如何强制卸载模块?
下面是模块的基本知识:
extern struct module __this_module;  
#define THIS_MODULE (&__this_module);  
enum module_state{   
MODULE_STATE_LIVE; // 模块存活,0   
MODULE_STATE_COMING; // 正在加载模块,1   
MODULE_STATE_GOING; // 正在卸载模块,2  
};   
struct module {   
enum module_state state; // 模块状态   
/* Member of list of modules */   
struct list_head list; // 内核模块链表   
/* Unique handle for this module */   
char name[MODULE_NAME_LEN]; //模块名称   
...  
#ifdef CONFIG_MODULE_UNLOAD  
/* What modules depend on me? */   
struct list_head modules_which_use_me;  
/* Who is waiting for us to be unloaded */   
struct task_struct *waiter;  
/* Destruction function. */   
void (*exit) (void);  
#ifdef CONFIG_SMP   
char *refptr;  
#else   
local_t ref;  
#endif  
#endif   
...  
};  
static inline local_t *__module_ref_addr(struct module *mod, int cpu)  
{#ifdef CONFIG_SMP  
return (local_t *) (mod->refptr + per_cpu_offset(cpu));  
#else  
return &mod->ref;  
#endif  
}  
但若仅将设置模块的引用计数和状态为0,还是会出现sys_delete_module因调用模块exit的函数(即vms_cleanup函数)而出现宕机的问题(因为程序出现oops,导致input_register_device(vms_input_dev); 根本没有执行,而该exit函数却调用了input_unregister_device(vms_input_dev); ,从而导致宕机)。为了解决这个问题,需要把exit换成一个能成功执行的函数。但问题依然没能完全解决,虽然此时可以rmmod vms成功,执行lsmod也查知vms在模块链表中已经删除,但vms模块因为在oops之前已经执行过 vms_dev = platform_device_register_simple("vms", -1, NULL, 0); 仍然会导致后续再执行insmod ./vms.ko时失败,因为该注册函数对应的注销函数没有被调用到。vms_dev是一个内核变量符号,通过sudo cat /proc/kallsyms | grep vms_dev 可以得知vms_dev的地址。(注意,在ubuntu下,必须使用root权限才能获取到符号地址!)然后在force_rmmod.c的exit函数中将这个vms_dev注销掉。但这种做很麻烦,每次都要手动去获取vms_dev的地址。其实内核可以通过kallsyms_lookup_name函数获取到vms_dev的地址,见force_rmmod的exit代码。
在执行insmod ./force_rmmod.ko之后,便可以随心所欲地安装和卸载vms.ko了。
linux3.x内核如何强制卸载模块?
 
三、问题解决思路
1、首先弄懂内核模块为啥无法删除? rmmod是通过sys_delete_module来卸载模块的,删除内核模块的必要条件是该模块的引用计数为0,且状态为“MODULE_STATE_LIVE; // 模块存活,0”,然后才调用模块的exit函数。如果执行exit时失败,甚至系统宕机,我们需要分析模块的init函数,仔细分析其流程,设计我们自己的exit函数。
2、执行cat /proc/kallsyms | grep vms_dev,结果发现其符号地址为0。原来是ubuntu的安全机制处理的结果,需要root权限才能查看地址。其对应的内核中获取符号地址的方法是:kallsyms_lookup_name函数。
3、程序出现rmmod失败的原因一是卸载的模块被其他模块引用,或者模块的初始化代码出现Bug,虽没有其他模块引用,但其模块引用计数也是1。所以需要仔细排查模块初始化代码中是否有异常退出的情况发生。
 
四、force_rmmod.c对应的Makefile(vms.c对应的Makefile类似)
KVERS = $(shell uname -r)  
obj-m := force_rmmod.o
all: kernel_modules
kernel_modules:  
make -C /lib/modules/$(KVERS)/build M=$(shell pwd) modules  
clean:  
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
 
本文永久更新地址:http://www.linuxdiyf.com/linux/22547.html