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

一、问题现象:

在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_modules

kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(shell pwd) modules
clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值