首页 > 代码库 > Linux Kernel Module(LKM) Init、Delete Code Principle Learning

Linux Kernel Module(LKM) Init、Delete Code Principle Learning

目录

1. Linux模块(LKM)简介2. 使用Linux模块3. LKM模块加载原理4. LKM模块卸载原理

 

1. Linux模块(LKM)简介

模块是一种向linux内核添加"设备驱动程序"、"文件系统"、"其他组件"的有效方法,而无须重新编译内核或重启系统,这消除了许多限制,同时带来了很多的优点

1. 通过使用模块,内核程序员能够预先编译大量驱动程序,而不会致使内核映像的尺寸发生膨胀。在自动检测硬件或用户提示后,安装例程会选择适当的模块并将其添加到内核中2. 内核开发者可以将试验性的代码打包到模块中,模块可疑卸载,修改代码或重新打包后再重新加载,这使得可以快速测试新特性,无需每次都重启系统3. 模块(LKM)可疑无缝地插入到内核中,同时模块也可以导出一些函数,可以由其他核心模块(以及持久编译到内核中的代码)使用。在模块代码需要卸载时,模块和内核剩余部分之间的关联会自动终止

0x1: 模块的依赖关系和引用

如果模块B使用了模块A提供的函数,那么模块A和模块B之间就存在关系,可以从两个方面来看这种关系

1. 模块B依赖模块A除非模块A已经驻留在内核内存,否则模块B无法装载2. 模块B引用模块A除非模块B已经移除,否则模块A无法从内核移除,在内核中,这种关系称之为"模块B使用模块A"

"struct module_use"和"struct module->module_which_use_me"这两个结果共同组合和保证了内核模块中的依赖关系。
如果模块B使用了模块A提供的函数,那么模块A和模块B之间就存在关系,可以从两个方面来看这种关系

1. 模块B依赖模块A除非模块A已经驻留在内核内存,否则模块B无法装载2. 模块B引用模块A除非模块B已经移除,否则模块A无法从内核移除,在内核中,这种关系称之为"模块B使用模块A"

对每个使用了模块A中函数的模块B,都会创建一个module_use结构体实例,该实例将被添加到模块A(被依赖的模块)的module实例中的modules_which_use_me链表中,modules_which_use_me指向模块B的module实例

我们在编写并加载LKM模块的时候,一定要注意模块间的依赖关系,有时候还需要分步骤单独进行指定模块的加载,才能保证模块间的依赖关系的正确连接

 

2. 使用Linux模块

0x1: 模块的添加

从用户的角度来看,模块可以通过以下两个"指令”进行添加

1. modprobe它考虑了各个模块之间可能出现的依赖性(在一个模块依赖于一个或多个合作者模块的功能时),modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod(即modprobe只是对insmod的一个包装)2. insmodinsmod只加载一个单一的模块到内核中,且该模块只信赖内核中已经存在的代码(不管是通过模块动态加载的、还是持久编译到内核中的)

从内核系统的角度来看,模块的加载可以通过以下方法完成

1. init_module()init_module()是一个系统调用,用户空间的工具只需要提供二进制数据,所有其他工作(重定位、解决引用问题)由内核自身完成2. request_module()request_module()不是系统调用,它用于从内核端加载模块,它不仅用于加载模块,还用于实现热插拔功能

0x2: 模块的移除

从用户的角度来看,模块可以通过以下"指令”进行删除

1. rmmod

从内核系统的角度来看,模块的卸载可以通过以下方法完成

1. delete_module()delete_module()是一个系统调用,它用于从内核移除一个模块,前提是该模块代码不再使用,且其他模块也不再使用该模块导出的函数(即不能有依赖关系)

 

3. LKM模块加载原理

LKM模块的加载的大部分逻辑都在init_module()中,ring3的insmod、modprobe仅仅负责传递一个二进制数据,本文只关注init_module这个系统调用的代码逻辑,关于整个insmod模块加载过程的原理分析,请参阅

http://files.cnblogs.com/LittleHann/Modultils%E5%B7%A5%E5%85%B7%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E2%80%94%E2%80%94insmod%E7%AF%87.pdf

0x1: 代码流程

init_module()系统调用是用户空间和内核之间用于装载新模块的接口,它的大致流程如下

 

0x2: 内核代码分析

\linux-2.6.32.63\kernel\module.cSYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs)    1. *umod    指向用户地址空间中的区域,表示模块的名字    2. len    该区域的长度    3. *uargs    指向字符串的指针,指定了模块的参数

\linux-2.6.32.63\kernel\module.c

/* This is where the real work happens */SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs){    struct module *mod;    int ret = 0;    /*    Must have permission     确保有插入和删除模块不受限制的权利,并且模块没有被禁止插入或删除    */    if (!capable(CAP_SYS_MODULE) || modules_disabled)    {        return -EPERM;    }            /* Only one module load at a time, please */    if (mutex_lock_interruptible(&module_mutex) != 0)        return -EINTR;    /*     Do all the hard work     分配,加载模块,并创建相关的sysfs文件    */    mod = load_module(umod, len, uargs);    if (IS_ERR(mod))     {        mutex_unlock(&module_mutex);        return PTR_ERR(mod);    }    /* Drop lock so they can recurse */    mutex_unlock(&module_mutex);    /*    通知内核通知链module_notify_list上的监听者,模块状态变为MODULE_STATE_COMING    关于module的状态信息,请参阅http://www.cnblogs.com/LittleHann/p/3865490.html,搜索: struct module         enum module_state        {            MODULE_STATE_LIVE,    //模块当前正常使用中(存活状态)             MODULE_STATE_COMING,    //模块当前正在被加载            MODULE_STATE_GOING,    //模块当前正在被卸载        };    */    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod);    //调用本模块的所有构造器    do_mod_ctors(mod);    /*     Start the module     调用模块的init方法    */    if (mod->init != NULL)        ret = do_one_initcall(mod->init);    if (ret < 0)    {        /* Init routine failed: abort.  Try to protect us from                   buggy refcounters. */        mod->state = MODULE_STATE_GOING;        synchronize_sched();        module_put(mod);        blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod);        mutex_lock(&module_mutex);        free_module(mod);        mutex_unlock(&module_mutex);        wake_up(&module_wq);        return ret;    }    if (ret > 0)     {        printk(KERN_WARNING"%s: ‘%s‘->init suspiciously returned %d, it should follow 0/-E convention\n""%s: loading module anyway...\n",               __func__, mod->name, ret,               __func__);        dump_stack();    }    /*     Now it‘s a first class citizen!  Wake up anyone waiting for it.      */    mod->state = MODULE_STATE_LIVE;    //唤醒module_wq 队列上等待本模块初始化的所有任务    wake_up(&module_wq);    //通知内核通知链module_notify_list上的监听者,模块状态变为MODULE_STATE_LIVE    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_LIVE, mod);    /*     We need to finish all async code before the module init sequence is done     等待所有的异步函数调用完成    */    async_synchronize_full();    //获得module_mutex锁,module_mutex作用之一就是保护全局的模块链表    mutex_lock(&module_mutex);    /* Drop initial reference. */    module_put(mod);    trim_init_extable(mod);#ifdef CONFIG_KALLSYMS    mod->num_symtab = mod->core_num_syms;    mod->symtab = mod->core_symtab;    mod->strtab = mod->core_strtab;#endif    /*    释放与模块初始化相关的节区所占的内存    这点和windows下的驱动加载是类似的,仅仅用于驱动加载的那部分"派遣初始化函数"被分配到"可换页内存"区域中,当驱动加载完毕后就立即释放    */    module_free(mod, mod->module_init);    mod->module_init = NULL;    mod->init_size = 0;    mod->init_text_size = 0;    mutex_unlock(&module_mutex);    return 0;}

Relevant Link:

http://bbs.chinaunix.net/thread-2194837-1-1.htmlhttp://blog.csdn.net/wuhui_gdnt/article/details/5316616http://blog.csdn.net/muge0913/article/details/7518568http://blog.csdn.net/ganggexiongqi/article/details/6823960

 

4. LKM模块卸载原理

我们输入指令rmmod,最终在系统内核中需要调用sys_delete_module进行实现

SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags)    1. name_user    待卸载的模块名称    2. flags

\linux-2.6.32.63\kernel\module.c

SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags){    struct module *mod;    char name[MODULE_NAME_LEN];    int ret, forced = 0;    //确保有插入和删除模块不受限制的权利,并且模块没有被禁止插入或删除    if (!capable(CAP_SYS_MODULE) || modules_disabled)        return -EPERM;    //获得从用户空间传递到内核空间的模块名字     if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)        return -EFAULT;    name[MODULE_NAME_LEN-1] = \0;    /* Create stop_machine threads since free_module relies on     * a non-failing stop_machine call. */    ret = stop_machine_create();    if (ret)        return ret;    //获得module_mutex锁     if (mutex_lock_interruptible(&module_mutex) != 0) {        ret = -EINTR;        goto out_stop;    }    //得到要卸载的模块的指针    mod = find_module(name);    if (!mod) {        ret = -ENOENT;        goto out;    }    /*    检查,确认没有其他模块依赖要卸载的模块    关于linux下模块间的依赖性以及相关数据结构,请参阅另一篇文章    http://www.cnblogs.com/LittleHann/p/3865490.html,搜索: struct module_use    */    if (!list_empty(&mod->modules_which_use_me)) {        /* Other modules depend on us: get rid of them first. */        ret = -EWOULDBLOCK;        goto out;    }    /*     Doing init or already dying?     检查模块的状态是否是 MODULE_STATE_LIVE     */    if (mod->state != MODULE_STATE_LIVE) {        /* FIXME: if (force), slam module count and wake up                   waiter --RR */        DEBUGP("%s already dying\n", mod->name);        ret = -EBUSY;        goto out;    }    /* If it has an init func, it must have an exit func to unload */    if (mod->init && !mod->exit) {        forced = try_force_unload(flags);        if (!forced) {            /* This module can‘t be removed */            ret = -EBUSY;            goto out;        }    }    /*     Set this up before setting mod->state     设置等待本模块退出 的进程为current    */    mod->waiter = current;    /* Stop the machine so refcounts can‘t move and disable module. */    ret = try_stop_module(mod, flags, &forced);    if (ret != 0)        goto out;    /*     Never wait if forced.     等待模块的引用计数变为0    */    if (!forced && module_refcount(mod) != 0)    {        wait_for_zero_refcount(mod);    }     //释放module_mutex锁     mutex_unlock(&module_mutex);    /*     Final destruction now noone is using it.     调用模块本身的exit函数    */    if (mod->exit != NULL)    {        mod->exit();    }     //告诉通知链module_notify_list上的监听者,模块状态 变为 MODULE_STATE_GOING    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod);    //等待所有的异步函数调用完成    async_synchronize_full();    mutex_lock(&module_mutex);    /* Store the name of the last unloaded module for diagnostic purposes */    strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));    free_module(mod); out:    mutex_unlock(&module_mutex);out_stop:    stop_machine_destroy();    return ret;}

 

Copyright (c) 2014 LittleHann All rights reserved