好文档 - 专业文书写作范文服务资料分享网站

内核模块的编写和运行

天下 分享 时间: 加入收藏 我要投稿 点赞

. .

第五章 模块编程实验

【实验目的】

通过学习核模块的编写和运行,了解模块是Linux OS的一种特有的机制,可根据用户的实际需要在不需要对核进行重新编译的情况下,模块能在核中被动态地加载和卸载。编写一个模块,将它作为Linux OS核空间的扩展来执行,并通过insmod

命令来手工加载,通过命令rmmod来手工卸载。

【准备知识】

Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合。在装载这些模块时,将它的代码到核中。Linux模块有两种装载方式:静态装载(核启动时装载)和动态装载(在核运行过程中装载)。若在模块装载之前就调用了动态模块的一个函数,则此调用将失败;若模块已被装载,则核就可以使用系统调用,并将其传递到模块中的相应函数。模块通常用来实现设备驱动程序(这要求模块的API和设备驱

动程序的API相一致)。模块可用来实现所期望的任何功能。

一.模块的组织结构

模块一旦被装载进系统,就在核地址空间中管态下执行。它就像任何标准的核代码一样成为核的一部分,并拥有其它核代码相同的权限和职责(当然也会引起系统的崩溃)。若模块知道核数据结构的地址,则它可以读写核数据结构。但Linux是一个整体式的核(monolithic kernel)结构,整个核是一个单独的且非常大的程序,从而存在一个普遍的问题:在一个文件中实现的函数可能需要在其它文件中定义的数据。在传统的程序中,这个问题是通过编辑器在生成可执行对象文件时,使用编辑器可以解析的外部(全局)变量来解决的。又因为模块的设计和实现与核无关,所以模块不能靠静态通过变量名引用核数据结构。恰好相反,Linux核采用了另外一种机制:实现数据结构的文件可以导出结构的符号名(可以从文件/proc/ksyms或文件/…/kernel/ksyms.c中以文本方式读取这个公开符号表),这样在运行时就可以使用这个结构了。不过在编写模块的过程中,编写(修改)导出变量时要格外注意,因为通过修改变量会修改核的状态,其结果可能并不是核设计者所期望的。在确信自己了

模块作为一种抽象数据类型,它具有一个可以通过静态核中断的接口。最小的模块结构必须包括两个函数,它们在系统装载模块和卸载模块时调用,分别是

. . .

解修改核变量的后果之前,应该对这些变量只进行读操作。

. .

init_module()和cleanup_module()。可以编写一个只包括这两个函数的模块,这样该模块中唯一会被调用的函数就是模块被装载时所调用的函数init_module()和模块被卸载时所调用的函数cleanup_module()。并且用函数init_module()来启动模块

由于模块可以实现相当复杂的功能,故可以在模块中加入很多新函数以实现所要期望的功能。不过加入模块的每个新函数都必须在该模块装载到核中时进行注册。若该模块是静态装载的,则该模块的所有函数都是在核启动时进行注册的;若该模块是动态装载的,则这些新函数必须在装载这个模块时动态注册。当然,如果该模块被动态卸载了,则该模块的函数都必须从系统中注销。通过这种方式,当这个模块不在系统中时,就不能调用该模块的函数。其中注册工作通常是在函数init_module()中完

装载期间的操作,用函数cleanup_module()来停止这些操作。

成的,而注销工作通常是在函数cleanup_module()中完成的。

由上述定义的模块应有如下的格式:

#include

#include

… … // 其它header信息

int init_module( )

{

… … // 装载时,初始化模块的编码

}

… …

… … // 期望该模块所能实现的一些功能函数,如open()、release()、write()、

// read()、ioctl()等函数

… …

void cleanup_module( )

{

… … // 卸载时,注销模块的编码

} 。

二.模块的编译

一旦设计并编写好模块,必须将其编译成一个适合核装载的对象文件。由于编写模块是用C语言来完成的,故采用gcc编译器来进行编译。若需要通知编译程序把这个模块作为核代码而不是普通的用户代码来编译,则就需向gcc编译器传递参数“-D_

. . .

. .

_KERNEL_ _”;若需要通知编译程序这个文件是一个模块而不是一个普通文件,则就需向gcc编译器传递参数“-DMODULE”;若需要对模块程序进行优化编译、连接,则就需使用“-O2”参数;若还需要对装载后的模块进行调试,则就需使用“-g”参数;同时需要使用“-Wall”参数来向装载程序传递all,使用“-c”开关通知编译程序

在编译完这个模块文件后不调用程序。

一般编译模块文件的命令格式如下:

#gcc -O2 -g -Wall -DMODULE -D_ _KERNEL_ _ -c filename.c

//filename.c为自己编写的模块程序源代码文件

执行命令后就会得到文件filename.o,该文件就是一个可装载的目标代码文件。

三.模块的装载

核模块的装载方式有两种。一种是使用insmod命令手工装载模块;另一种是请求装载demand loading(在需要时装载模块),即当有必要装载某个模块时,若用户安装了核心中不存在的文件系统时,核心将请求核守护进程kerneld准备装载适当的模块。该核守护进程是一个带有超级用户权限的普通用户进程。此实验中我们主要采

系统启动时,kerneld开始执行,并为核打开一个IPC通道,核通过向kerneld发送消息请求执行各种任务。kerneld的主要功能是装载和卸载核模块,kerneld自身并不执行这些任务,它通过某些程序(如insmod)来完成。Kerneld只是核的代理,

insmod程序必须找到请求装载的核模块(该请求装载的模块一般被保存在/lib/modules/kernel-version中)。这些模块与系统中其它程序一样是已连接的目标文件,但不同的是它们被连接成可重定位映象(即映象没有被连接到在特定的地址上运行,其文件格式是a.out或ELF)。亦就是说,模块在用户空间(使用适当的标志)进行编译,结果产生一个可执行格式的文件。在用insmod命令装载一个模块时,

用insmod命令手工装载模块。

只为核进行调度。

将会发生如下事件:

(2)insmod执行一个特权级系统调用get_kernel_syms()函数以找到核的输出(3)create_module()为这个模块分配存空间,并将新模块添加在核模块链表的(4)通过init_module()系统调用装载模块。(该模块定义的符号在此时被导出,

(1)新模块(通过核函数create_module())加入到核地址空间。

符号(一个符号表示为符号名和符号值,如地址值)。

尾部,然后将新模块标记为UNINITIALIZED(模块未初始化)。

供其它可能后来装载的模块使用)

. . .

. .

(5)insmod为新装载的模块调用init_module()函数,然后将新模块标志为在执行完insmod命令后,就可在/proc/modules文件中看到装载的新模块了。(为证实其正确性,可在执行insmod命令之前先查看/proc/modules文件,执行之后再

RUNNING(模块正在运行)。

查看比较)

四.模块的卸载

当一个模块不需要使用时,可以使用rmmod命令卸载该模块。由于无需连接,故它的任务比加载模块要简单得多。但如果请求装载模块在其使用计数为0时,kerneld将自动从系统中卸载该模块。卸载时调用模块的cleanup_module()释放分配给该模块的核资源,并将其标志为DELETED(模块被卸载);同时断开核模块链表中的连接,

修改它所依赖的其它模块的引用,重新分配模块所占的核存。

五.模块连接到核的示意图

该图比较明显地展示了模块连接到核所使用的命令和函数,以及各个函数之间的调用关系。通过该图,可以比较清晰地看出模块连接到核的整个连接过程,这也有助于核模块的编写。

模块 内核核心 register_capability() init_module() . . .

insmod capabilities printk( ) … . .

六.模块程序中管理模块的几个文件操作

在核部用一个file结构来识别模块,而且核使用file_operatuions结构来访问模块程序中的函数。file_operatuions结构是一个定义在中的函数指针表。管理模块的文件操作,通常也称为“方法”,它们都为struct file_operations提供函数指针。在struct file_operations中的操作一般按如下顺序出现,除非说明,它们返回0值时表示访问成功;发生错误时返回一个负的错误值(目前共有13

个操作):

int (*lseek) ()、int (*read)()、int (*write)()、int (*readdir)()、int (*select)()、int (*ioctl)()、int (*mmap)()、int (*open)()、void (*release)()、int (*fsync)()、int (*fasync)()、int (*check_media_change)()、int

(*revalidate)()

下面我们只简单介绍其中的几个操作,其它在以后涉及时再介绍:

1、方法int (*read)(struct inode *,struct file *,char *, int)

该方法用来从模块中读取数据。当其为NULL指针时将引起read系统调用返回

-EINVAL(“非法参数”)。函数返回一个非负值表示成功地读取了多少字节。

2、方法int (*write)(struct inode *,struct file *,const char *, int)

该方法用来向模块发送数据。当其为NULL指针时将引起write系统调用返回

. . .

内核模块的编写和运行

..第五章模块编程实验【实验目的】通过学习核模块的编写和运行,了解模块是LinuxOS的一种特有的机制,可根据
推荐度:
点击下载文档文档为doc格式
1nifb4eoq844p5c1cp2i5zpak1cslt00dez
领取福利

微信扫码领取福利

微信扫码分享