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

来源:互联网 发布:快3遗漏号码数据查询 编辑:程序博客网 时间:2024/06/08 06:39

一、问题现象:

在insmod时调用的init函数代码执行过程中出现oops,导致rmmod卸载失败,此时不得不重启目标板?

No!

下面是《精通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看到的信息:


然后sudo rmmod vms模块,会发现卸载不了:


我们知道,rmmod是调用sys_delete_module函数进行删除模块的,下面是其具体实现的的解析:


所以需要通过编写专门用于卸载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。


下面是模块的基本知识:

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了。


三、问题解决思路

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_moduleskernel_modules:make -C /lib/modules/$(KVERS)/build M=$(shell pwd) modulesclean:make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean


0 0
原创粉丝点击