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

15 篇文章 0 订阅
12 篇文章 0 订阅
一、问题现象:

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

No!

下面是《精通linux设备驱动程序开发》中模拟鼠标的输入设备驱动的内核模块vms.c代码:
[html] view plaincopy在CODE上查看代码片派生到我的代码片

    #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的源代码:
[html] view plaincopy在CODE上查看代码片派生到我的代码片

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

下面是模块的基本知识:
[html] view plaincopy在CODE上查看代码片派生到我的代码片

    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类似)
[html] view plaincopy在CODE上查看代码片派生到我的代码片

    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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值