目录
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. insmod insmod只加载一个单一的模块到内核中,且该模块只信赖内核中已经存在的代码(不管是通过模块动态加载的、还是持久编译到内核中的)
从内核系统的角度来看,模块的加载可以通过以下方法完成
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.c SYSCALL_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.html http://blog.csdn.net/wuhui_gdnt/article/details/5316616 http://blog.csdn.net/muge0913/article/details/7518568 http://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
Linux Kernel Module(LKM) Init、Delete Code Principle Learning,布布扣,bubuko.com