Chinaunix首页 | 论坛 | 博客
  • 博客访问: 122472
  • 博文数量: 41
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 306
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-01 10:41
文章分类

全部博文(41)

文章存档

2011年(1)

2009年(40)

我的朋友

分类:

2009-04-10 16:27:39

简介
模块(module)是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。

使用模块的优点:
1,将来修改内核时,不必全部重新编译整个内核,可节省不少时间
2,系统中如果需要使用新模块,不必重新编译内核,只要插入相应的模块即可

模块的使用方式:(modules-1.3.57.tar.gz)
 命令 功能
备注
 modprobe program to add and remove modules from the Linux Kernel
 
 depmod program to generate modules.dep and map files.Make module dependency file. dependency file 就在/lib/modules/'uname -r'/modules.dep 
 insmod simple program to insert a module into the Linux Kernel
 
 rmmod  simple program to remove a module from the Linux Kernel  
 lsmod program to show the status of modules in the Linux Kernel  
     
 
kerneld 是一个 daemon,可负责自动调用insmod 来插入 module,是一个很方便的 daemon。它也同时查看,若某个 module 插入后但很久都没人在用,就会将它卸载,以节省内存空间。

相关文件:
/etc/rc.d/ rc.modules
/etc/rc.d/rc.S or /etc/rc.d/rc.syinit
/etc/conf.modules or /etc/modules.conf

模块的装载/卸载:
静态:在系统启动时就装载
动态:使用insmod等命令在系统运行过程中装载

注:
1.现在kerneld已经被kmod所取代,原因主要是kerneld是使用System V IPC,经过了中间层,比较复杂。
2.需要超级用户权限。
3.形式:
Module: #pages: Used by:
msdos 5 1
vfat 4 1 (autoclean)
fat 6 [vfat msdos] 2 (autoclean)

工作原理
摘要:
*内核符号表
*模块装载
*模块卸载
*多个模块间的依赖关系
Kernel 里有一个变量叫 module_list,每当 user 将一个 module 载到 kernel 里的时候,这个 module 就会被记录在 module_list 里面。当 kernel 要使用到这个 module 提供的 function 时,它就会去 search 这个 list,找到 module,然后再使用其提供的 function 或 variable。每一个 module 都可以 export 一些 function 或变量来让别人使用。除此之外,module 也可以使用已经载到 kernel 里的 module 提供的 function。这种情形叫做 module stack。比方说,module A 用到 module B 的东西,那在加载 module A 之前必须要先加载 module B。否则 module A 会无法加载。除了 module 会 export 东西之外,kernel 本身也会 export 一些 function 或 variable。同样的,module 也可以使用 kernel 所 export 出来的东西。由于大家平时都是撰写 user space 的程序,所以,当突然去写 module 的时候,会把平时写程序用的 function 拿到 module 里使用。像是 printf 之类的东西。我要告诉各位的是,module 所使用的 function 或 variable,要嘛就是自己写在 module 里,要嘛就是别的 module 提供的,再不就是 kernel 所提供的。你不能使用一般 libc 或 glibc所提供的 function。像 printf 之类的东西。这一点可能是各位要多小心的地方。

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

Linux为我们提供了两个命令:使用insmod来显式加载核心模块,使用rmmod来卸载模块。同时核心自身也可以请求核心后台进程kerneld来加载与卸载模块。
动态可加载代码的好处在于可以让核心保持很小的尺寸同时非常灵活。在我的Intel系统中由于使用了模块,整个核心仅为406K字节长。由于我只是 偶尔使用VFAT文件系统, 所以我将Linux核心构造成当mount VFAT分区时自动加载VFAT文件系统模块。当我卸载VFAT分区时系统将检测到我不再需要VFAT文件系统模块,将把它从系统中卸载。模块同时还可以 让我们无需重构核心并频繁重新启动来尝试运行新核心代码。尽管使用模块很自由,但是也有可能同时带来与核心模块相关的性能与内存损失。可加载模块的代码一 般有些长并且额外的数据结构可能会占据一些内存。同时对核心资源的间接使用可能带来一些效率问题。
一旦Linux模块被加载则它和普通核心代码一样都是核心的一部分。它们具有与其他核心代码相同的权限与职 责;换句话说Linux核心模块可以象所有核心代码和设备驱动一样使核心崩溃。

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

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

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


1 模块的加载

                                    图1 核心模块链表

核心模块的加载方式有两种。首先一种是使用insmod命令手工加载模块。另外一种则是在需要时加载模块;我们称它为请求加载。当核心发现有必要加载某个 模块时,如用户安装了核心中不存在的文件系统时,核心将请求核心后台进程(kerneld)准备加载适当的模块。这个核心后台进程仅仅是一个带有超级用户 权限的普通用户进程。当系统启动时它也被启动并为核心打开了一个进程间通讯(IPC)通道。核心需要执行各种任务时用它来向kerneld发送消息。
kerneld的主要功能是加载和卸载核心模块, 但是它还可以执行其他任务, 如通过串行线路建立PPP连接并在适当时候关闭它。kerneld自身并不执行这些任务,它通过某些程序如insmod来做此工作。它只是核心的代理,为核心进行调度。
insmod 程序必须找到要求加载的核心模块。请求加载核心模块一般被保存在/lib/modules/kernel-version 中。这些核心模块和系统中其他程序一样是已连接的目标文件,但是它们被连接成可重定位映象。即映象没有被连接到在特定地址上运行。这些核心模块可以是 a.out或ELF文件格式。insmod将执行一个特权级系统调用来找到核心的输出符号。这些都以符号名以及数值形式,如地址值成对保存。核心输出符号 表被保存在核心维护的模块链表的第一个module结构中,同时module_list指针指向此结构。只有特殊符号被添加到此表中,它们在核心编译与连 接时确定,不是核心每个符号都被输出到其模块中。例如设备驱动为了控制某个特定系统中断而由核心例程调用的"request_irq"符号。在我的系统 中,其值为0x0010cd30。我们可以通过使用ksyms工具或者查看/proc/ksyms来观看当前核心输出符号。ksyms工具既可以显示所有核心输出符号也可以只显示那些已加载模块的符号。insmod将模块读入虚拟内存并通过使用来自核心输出符号来修改其未解析的核心例程和资源的引用地址。 这些修改工作采取由insmod程序直接将符号的地址写入模块中相应地址来修改内存中的模块映象。
当insmod修改完模块对核心输出符号的引用后,它将再次使用特权级系统调用来申请足够的空间来容纳新核心。核心将为其分配一个新的module结构以及足够的核心内存来保存新模块, 并将它放到核心模块链表的尾部。 然后将其新模块标志为UNINITIALIZED。

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

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

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

2 模块的卸载

模块可以通过使用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)

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

编程实现


模块的组织结构
2.0/2.2
至少需要两个函数:init_module()和cleanup_module()。一般在init_module()完成初始化工作,例如内存分配(kmalloc);在cleanup_module()中完成回收工作。

/* The necessary header files */

/* Standard in kernel modules */
#include /* We e doing kernel work */
#include /* Specifically, a module */


/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS /这两行应该修改为#ifdef MODVERSIONS
#include
#endif


/* Initialize the module */
int init_module()
{
Printk("Hello, world - this is the kernel speakingn");

/* If we return a non zero value, it means that
* init_module failed and the kernel module
* can be loaded */

Return 0;
}


/* Cleanup - undid whatever init_module did */
void cleanup_module()
{
Printk("Short is the life of a kernel modulen");
}


2.3.*/2.4.*中的用法:
2.3/2.4中的用法不同,应该使用:
module_init(init_proc_fs)
module_exit(exit_proc_fs)
优点:有了返回值。

static int __init init_proc_fs(void)
{
int err = register_filesystem(&proc_fs_type);
if (!err) {
proc_mnt = kern_mount(&proc_fs_type);
err = PTR_ERR(proc_mnt);
if (IS_ERR(proc_mnt))
unregister_filesystem(&proc_fs_type);
Else
err = 0;
}
return err;
}

static void __exit exit_proc_fs(void)
{
unregister_filesystem(&proc_fs_type);
kern_umount(proc_mnt);
}

module_init(init_proc_fs)
module_exit(exit_proc_fs)


模块的Makefile
Options:
-D__KERNEL__ / in kernel space
-DMODULE / create module
-DLINUX / which can be compiled on more than one operating system
-D__SMP__ / Symmetrical MultiProcessing
-DCONFIG_MODVERSIONS / should include /usr/include/linux/modversions.h
fomit-frame-pointer告诉gcc不要为那些不需要的函数保存页面指针.这会使得我们的寄存器在调用init_module以后保持不变.

阅读(822) | 评论(0) | 转发(0) |
0

上一篇:获得git

下一篇:Linux内核模块编程(二)

给主人留下些什么吧!~~