Chinaunix首页 | 论坛 | 博客
  • 博客访问: 59359
  • 博文数量: 27
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 300
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-24 17:31
文章分类
文章存档

2011年(1)

2010年(8)

2009年(18)

我的朋友

分类: LINUX

2009-05-10 22:07:55

 
 

    kerneld mini-HOWTO

kerneld 是什么?

kerneld 是由 引入 1.3 版的发展核心 (development kernel) 的功能。它可在所有 2.0 2.1 版本的核心找到。它令模块 (modules)─即驱动程序 (device drivers)、网络驱动器 (network drivers)、文档系统 (filesystems)─自动在有需要时载入,而不需自行使用 modprobe insmod 

     linux 核心

第十四章 Linux核心资源

模块

内核模块代码部分位于modules包中。代码位于kernel/modules.c,数据结构与内核后台进程kerneld的头文件位于include/linux/module.hinclude/linux/kerneld.h目录中。必要时需查阅 include/linux/elf.h中的ELF文件格式。

 

 

 

第十二章 模块


  本章主要描叙Linux内核动态加载功能模块(如文件系统模块)的工作原理。

Linux内核是一种monolithic类型的内核,即单一的大内核,内核中所有的功能部件都可以对其全部内部数据结构和例程进行访问。(操作系统内核的另外一种形式是微内核结构,此时内核的所有功能部件都被拆分成独立部件,各部件之间通过严格的通讯机制进行联系。这样通过配置程序将新部件加入内核的方式非常耗时。比如说我们想为一个NCR 810 SCSI卡配置SCSI驱动,但是内核中没有这个部分。那么我们必须重新配置并重构内核。)。Linux内核可以让我们可以随意动态的加载与卸载操作系统部件。Linux模块就是这样一种可在系统启动后的任何时候动态连入内核的代码块。当我们不再需要它时又可以将它从内核中卸载并删除。Linux模块多指设备驱动、伪设备驱动, 如网络设备和文件系统。

 

  Linux为我们提供了两个命令:使用insmod来显式加载核心模块,使用rmmod来卸载模块。同时内核自身也可以请求内核后台进程kerneld来加载与卸载模块。

动态可加载代码的好处在于可以让内核保持很小的尺寸同时又非常灵活。在我的Intel系统中由于使用了模块,整个内核仅为406K字节长。由于我只是偶尔使用VFAT文件系统, 所以我将Linux内核构造成 mount VFAT分区时自动加载VFAT文件系统模块。当我卸载VFAT分区时系统将检测到我不再需要VFAT文件系统模块,会把它从系统中卸载。模块同时还可以让我们无需重构内核并无需重新启动linux即可尝试运行新内核代码。尽管使用模块很自由,但是内核模块也可能带来相关的性能和内存方面的损失: 可加载模块的代码一般有些长并且额外的数据结构可能会占据一些内存。同时对内核资源的间接使用可能带来一些效率问题。

 

  一旦Linux模块被加载则它和其他内核代码一样都是内核的一部分。它们具有与其他内核代码相同的权限与职责;换句话说Linux内核模块可以象所有内核代码和设备驱动一样使内核崩溃。

 

  模块为了使用其所需的内核资源,所以模块必须能够找到它们在内存中的位置。例如模块需要调用内核的内存分配例程kmalloc()来分配内存。模块文件在创建时并不知道kmalloc()在内存中何处,这样内核必须在使用这些模块前,先修改这些模块中对 kmalloc()的引用地址。内核中有1内核符号表中维护着一个内核资源链表,这样当加载模块时内核能够解析出模块中对内核资源的引用。Linux还允许存在模块堆栈,它在模块之间相互调用时使用。例如VFAT文件系统模块可能需要FAT文件系统模块的服务,因为VFAT文件系统多少是从FAT文件系统中扩展而来。某个模块对其他模块的服务或资源的需求类似于模块对内核本身资源或服务的请求。不过此时所请求的服务是来自另外一个事先已加载的模块。每当加载模块时,内核将把新近加载模块输出的所有资源和符号添加到内核符号表中。

 

  当试图卸载某个模块时,内核需要知道此模块是否已经没有被使用,同时它需要有种方法来通知这个将要被卸载的模块。模块必须能够在从内核中删除之前释放其分配的所有系统资源,如内核内存或中断。当模块被卸载时,内核将从内核符号表中删除所有与之对应的符号。

 

  可加载模块具有使操作系统崩溃的能力,而编写的较差的模块会带来另外一种问题。当你在一个其它时间创建的内核上(而不是当前你运行的内核上)加载模块时将会出现什么结果?一种可能的情况是模块会调用具有错误参数的内核例程。内核应该使用严格的版本控制来对加载模块进行检查以防止这种这些情况的发生。

 

模块的加载


12.1 核心模块链表

 

  核心模块的加载方式有两种。首先一种是使用insmod命令手工加载模块。另外一种则是在需要时加载模块;我们称它为请求加载。当内核发现有必要加载某个模块时,例如用户安装了内核中不存在的文件系统时,内核将请求内核后台进程kerneld)准备加载适当的模块。这个内核后台进程仅仅是一个带有超级用户权限的普通用户进程。当Linux系统启动时它也被启动并为内核打开了一个进程间通讯(IPC)通道。内核需要执行各种任务时用IPC来向kerneld发送消息。

 

  kerneld的主要功能是加载和卸载核心模块, 但是它还可以执行其他任务, 如通过串行线路建立PPP连接并在适当时候关闭它。kerneld自身并不执行这些具体任务,它通过某些程序如insmod来做此工作。它只是内核的代理,为内核进行调度。

 

  insmod程序必须找到要求加载的内核模块。请求加载内核模块一般被保存在/lib/modules/kernel-version 中。这些内核模块和系统中其他程序一样是已连接的目标文件,但是它们被连接成可重定位映象(*.so)。即映象没有被连接到在特定地址上运行。这些内核模块可以是a.outELF文件格式。insmod将执行一个特权级系统调用来找到内核的输出符号。这些都以符号名以及数值形式(如地址值)成对保存。内核输出符号表被保存在内核维护的模块链表的第1module结构中,同时module_list指针指向此结构。只有特殊符号被添加到此表中(它们在内核编译与连接时确定),不是内核每个符号都被输出到其模块结构中。例如设备驱动为了控制某个特定系统中断而由内核例程调用的"request_irq"符号。在我的系统中,其值为0x0010cd30。我们可以通过使用ksyms工具或者查看/proc/ksyms来观看当前内核输出符号。ksyms工具既可以显示所有内核输出符号也可以只显示那些已加载模块的符号。insmod将模块读入虚拟内存并通过使用内核输出符号表来修改模块内尚未解析的内核例程和资源的引用地址。这些修改工作是由insmod程序修改内存中的模块映象,直接将符号的地址写入模块中相应地址。

 

  当insmod修改完模块对内核输出符号的引用后,它将再次使用特权级系统调用来申请足够的空间来容纳新内核。内核将为其分配一个新的module结构以及足够的内核内存来保存新模块, 并将它放到内核模块链表的尾部。然后将新模块的状态标志为UNINITIALIZED

 

  图12.1给出了一个加载两个模块:VFATFAT后的内核模块链表示意图。不过图中没有画出链表中的第一个模块:用来存放内核输出符号表的一个伪模块。lsmod可以帮助我们列出系统中所有已加载的内核模块以及相互间依赖关系。它是通过重新格式化从内核module结构中建立的/proc/modules来进行这项工作的。内核为其分配的内存被映射到insmod的地址空间, 这样它就能访问内核空间。insmod将模块拷贝到已分配空间中, 如果为它分配的内核内存已用完,则它将再次申请。不过不要指望多次将模块加载到相同地址,更不用说在两个不同 Linux系统的相同位置。另外此重定位工作包括使用适当地址来修改模块映象。

 

  这个新模块也希望将其符号输出到内核中,insmod将为模块构造输出符号映象表。每个内核模块必须包含模块初始化例程和模块清除例程,这2个例程的符号默认是不输出的, 但是insmod必须知道它们的地址, 这样insmod可以将它们传递给内核。所有这些工作做完之后,insmod将调用模块初始化代码并执行一个特权级系统调用将模块的初始化例程与清除例程地址传递给内核。

 

  当将一个新模块加载到内核中时,内核必须更新其内核符号表并修改那些被新模块使用的老模块。那些依赖于其他模块的模块必须在其符号表尾部维护一个引用链表并在其module数据结构中指向它。图12.1VFAT 依赖于FAT文件系统模块。所以FAT模块包含一个对VFAT模块的引用;这个引用在加载VFAT模块时添加。内核调用模块的初始化例程,如果成功它将安装此模块。模块的清除例程地址被存储在其module结构中,它将在模块卸载时由内核调用。最后模块的状态被设置成RUNNING

 

模块的卸载

  模块可以通过使用rmmod命令来手工删除,请求加载模块将被kerneld在其使用记数为0时自动从内核中删除。 kerneld在其每次idle定时器到期时都执行一个系统调用以将系统中所有不再使用的请求加载模块从系统中删除。这个定时器的值在启动kerneld时设置;我系统上的值为180秒。这样如果你安装一个iso9660 CDROM并且你的iso9660文件系统是一个可加载模块, 则在卸载CD ROM后的很短时间内此iso9660模块将从内核中删除。

 

  如果内核中的其他部分还在使用某个模块, 则此模块不能被卸载。例如如果你的系统中安装了多个VFAT文件系统则你将不能卸载VFAT模块。执行lsmod我们将看到每个模块的引用记数。如:

Module:        #pages:  Used by:

 

msdos              5                  1

 

vfat               4                  1 (autoclean)

 

fat                6    [vfat msdos]  2 (autoclean)

 

  此记数表示依赖此模块的内核实体个数。在上例中VFATmsdos模块都依赖于fat模块, 所以fat模块的引用记数为2vfatmsdos模块的引用记数都为1,表示各有一个已安装文件系统。如果我们安装另一个VFAT文件系统则vfat模块的引用记数将为2。模块的引用记数被保存在其映象的第一个长字中。这个字同时还包含AUTOCLEANVISITED标志。请求加载模块使用这两个标志域。如果模块被标记成AUTOCLEAN则内核知道此模块可以自动卸载。VISITED标志表示此模块正被一个或多个文件系统部分使用;只要有其他部分使用此模块则这个标志被置位。每次内核被kerneld要求将没有谁使用的请求加载模块删除时,内核将在所有模块中扫描可能的候选者。但是一般只查看那些被标志成AUTOCLEAN并处于RUNNING状态的模块。如果某模块的VISITED 标记被清除则它将被删除出去。如果某模块可以卸载,则可以调用其清除例程来释放掉分配给它的内核资源。它所对应的module结构将被标记成DELETED并从内核模块链表中断开。其他依赖于它的模块将修改它们各自的引用域来表示它们间的依赖关系不复存在。此模块需要的内核内存都将被回收。

 

 

转:Linux内核可加载模块剖析

http://www.ibm.com/developerworks/cn/linux/l-lkm/?S_TACT=105AGX52&S_CMP=tec-csdn%20

 

模块(*.so)结构

典型的程序有一个 main 函数,其中 LKM 包含 entry 和 exit 函数(在 2.6 版本,您可以任意命名这些函数)。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因为 entry 和 exit 函数是用户定义的,所以有 module_initmodule_exit 宏,用于定义这些函数属于哪种函数。LKM 还包含一组必需的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等。图 1 提供了一个非常简单的 LKM 的视图。

简单 LKM 的源代码视图

 

构建模块时,可以使用用户工具管理模块(尽管内部已经改变):

insmod(安装 LKM),rmmod (删除 LKM),modprobeinsmod 和 rmmod 的包装器),depmod(用于创建模块依赖项),modinfo(用于为模块宏查找值)。

 

在用户空间中,insmod启动模块加载过程。insmod 命令定义需要加载的模块,并调用 init_module 用户空间系统调用,开始加载过程。2.6 版本内核的 insmod 命令经过修改后变得非常简单(70 行代码),可以在内核中执行更多工作。insmod 并不进行所有必要的符号解析(处理 kerneld),它只是通过 init_module 函数将模块二进制文件复制到内核,然后由内核完成剩余的任务。

init_module 函数通过系统调用层,进入内核调用内核函数 sys_init_module(参见图 3)。这是加载模块的主要函数,它利用许多其他函数完成困难的工作。类似地,rmmod 命令会使 delete_module 执行 system call 调用,而 delete_module 最终会进入内核,并调用 sys_delete_module 将模块从内核删除。



加载和卸载模块时用到的主要命令和函数

在模块的加载和卸载期间,模块子系统维护了一组简单的状态变量,用于表示模块的操作。加载模块时,状态为 MODULE_STATE_COMING。如果模块已经加载并且可用,状态为 MODULE_STATE_LIVE。此外,卸载模块时,状态为 MODULE_STATE_GOING





现在,我们看看加载模块时的内部函数调用过程(参见图 4)。当调用内核函数 sys_init_module 时,会开始一个许可检查,查明调用者是否有权执行这个操作(通过 capable 函数完成)。然后,调用 load_module 函数,这个函数负责将模块加载到内核并执行必要的调试。load_module 函数返回一个指向最新加载模块的模块引用。这个模块加载到系统内具有所有模块的双向链表上,并且通过 notifier 列表通知正在等待模块状态改变的线程。最后,调用模块内的 init() 函数,更新模块状态,表明模块已经加载并且可用。



内部(简化的)模块加载过程 

 

Load_module加载模块的内部细节是 ELF 模块解析和操作。load_module 函数(位于 ./linux/kernel/module.c)首先分配一块用于容纳整个 ELF 模块的临时内存。然后,通过 copy_from_user 函数将 ELF 模块从用户空间读入到临时内存。作为一个 ELF 对象,这个文件的结构非常独特,易于解析和验证(第 1 步)。 下一步是对加载的 ELF 映像执行一组健康检查(它是有效的 ELF 文件吗?它适合当前的架构吗?等等)(第 2 步)。完成健康检查后,就会解析 ELF 映像,然后会为每个区段头创建一组方便变量,简化随后的访问。因为 ELF 对象的偏移量是基于 0 的(除非重新分配),所以这些方便变量将相对偏移量包含到临时内存块中。在创建方便变量的过程中还会验证 ELF 区段头,确保加载的是有效模块(第 3 步)。 任何可选的模块参数都从用户空间加载到另一个已分配的内核内存块(第 4 步),并且更新模块状态,表明模块已加载(MODULE_STATE_COMING)(第5步)。如果需要 per-CPU 数据(这在检查区段头时确定),那么就分配 per-CPU 块(第6步)。 在前面的步骤,模块区段被加载到内核(临时)内存,并且知道哪个section应该保留,哪个可以删除。步骤 7 为内存中的模块分配最终的位置,并移动必要的sectionELF 头中具有 SHF_ALLOC属性的section,或在执行期间占用内存的section)(第7步)。然后执行分配另一个内存块,大小是模块必要sections所需的大小。迭代遍历临时 ELF 块中的每个section,并将需要执行的section复制到新的块中(第8步)。接下来要进行一些额外的维护。同时还进行符号解析,可以解析位于内核中的符号(被编译成内核映象),或临时的符号(从其他模块导出)。然后为每个剩余的区段迭代新的模块并执行重新定位。这个步骤与架构有关,因此依赖于为架构(./linux/arch//kernel/module.c)定义的 helper 函数(第9步)。最后,刷新指令缓存(因为使用了临时 .text 区段)(第10步),执行一些额外的维护(释放临时模块内存,设置系统文件),并将模块最终返回到 load_module(第11步)。

 

 

 
阅读(896) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~