内核模块的编写和运行
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第五章模块编程实验
【实验目的】
通过学习内核模块的编写和运行,了解模块是Linux OS的一种特有的机制,可根据用户的实际需要在不需要对内核进行重新编译的情况下,模块能在内核中被动态地加载和卸载。编写一个模块,将它作为Linux OS内核空间的扩展来执行,并通过insmod命令来手工加载,通过命令rmmod来手工卸载。
【准备知识】
Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合。在装载这些模块时,将它的代码链接到内核中。Linux模块有两种装载方式:静态装载(内核启动时装载)和动态装载(在内核运行过程中装载)。若在模块装载之前就调用了动态模块的一个函数,则此调用将失败;若模块已被装载,则内核就可以使用系统调用,并将其传递到模块中的相应函数。模块通常用来实现设备驱动程序(这要求模块的API和设备驱动程序的API相一致)。模块可用来实现所期望的任何功能。
一.模块的组织结构
模块一旦被装载进系统,就在内核地址空间中管态下执行。它就像任何标准的内核代码一样成为内核的一部分,并拥有其它内核代码相同的权限和职责(当然也会引起系统的崩溃)。若模块知道内核数据结构的地址,则它可以读写内核数据结构。但Linux是一个整体式的内核(monolithic kernel)结构,整个内核是一个单独的且非常大的程序,从而存在一个普遍的问题:在一个文件中实现的函数可能需要在其它文件中定义的数据。在传统的程序中,这个问题是通过链接编辑器在生成可执行对象文件时,使用链接编辑器可以解析的外部(全局)变量来解决的。又因为模块的设计和实现与内核无关,所以模块不能靠静态链接通过变量名引用内核数据结构。恰好相反,Linux内核采用了另外一种机制:实现数据结构的文件可以导出结构的符号名(可以从文件/proc/ksyms或文件/…/kernel/中以文本方式读取这个公开符号表),这样在运行时就可以使用这个结构了。不过在编写模块的过程中,编写(修改)导出变量时要格外注意,因为通过修改变量会修改内核的状态,其结果可能并不是内核设计者
所期望的。在确信自己了解修改内核变量的后果之前,应该对这些变量只进行读操作。
模块作为一种抽象数据类型,它具有一个可以通过静态内核中断的接口。最小的
模块结构必须包括两个函数,它们在系统装载模块和卸载模块时调用,分别是
init_module()和cleanup_module()。可以编写一个只包括这两个函数的模块,这样
该模块中唯一会被调用的函数就是模块被装载时所调用的函数init_module()和模
块被卸载时所调用的函数cleanup_module()。并且用函数init_module()来启动模块
装载期间的操作,用函数cleanup_module()来停止这些操作。
由于模块可以实现相当复杂的功能,故可以在模块中加入很多新函数以实现所要
期望的功能。不过加入模块的每个新函数都必须在该模块装载到内核中时进行注册。
若该模块是静态装载的,则该模块的所有函数都是在内核启动时进行注册的;若该模
块是动态装载的,则这些新函数必须在装载这个模块时动态注册。当然,如果该模块
被动态卸载了,则该模块的函数都必须从系统中注销。通过这种方式,当这个模块不
在系统中时,就不能调用该模块的函数。其中注册工作通常是在函数init_module()
中完成的,而注销工作通常是在函数cleanup_module()中完成的。
由上述定义的模块应有如下的格式:
#include
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
//为自己编写的模块程序源代码文件
执行命令后就会得到文件,该文件就是一个可装载的目标代码文件。
三.模块的装载
内核模块的装载方式有两种。一种是使用insmod命令手工装载模块;另一种是
请求装载demand loading(在需要时装载模块),即当有必要装载某个模块时,若用
户安装了核心中不存在的文件系统时,核心将请求内核守护进程kerneld准备装载适
当的模块。该内核守护进程是一个带有超级用户权限的普通用户进程。此实验中我们
主要采用insmod命令手工装载模块。
系统启动时,kerneld开始执行,并为内核打开一个IPC通道,内核通过向kerneld
发送消息请求执行各种任务。kerneld的主要功能是装载和卸载内核模块,kerneld
自身并不执行这些任务,它通过某些程序(如insmod)来完成。Kerneld只是内核的
代理,只为内核进行调度。
insmod程序必须找到请求装载的内核模块(该请求装载的模块一般被保存在
/lib/modules/kernel-version中)。这些模块与系统中其它程序一样是已连接的目
标文件,但不同的是它们被连接成可重定位映象(即映象没有被连接到在特定的地址
上运行,其文件格式是或ELF)。亦就是说,模块在用户空间(使用适当的标志)进