实验五内核模块设计实验.docx
《实验五内核模块设计实验.docx》由会员分享,可在线阅读,更多相关《实验五内核模块设计实验.docx(17页珍藏版)》请在冰点文库上搜索。
实验五内核模块设计实验
实验五内核模块设计实验
现代的Linux内核是具有微内核特点的宏内核。
Linux内核作为一个大程序在内核空间运行。
太多的设备驱动和内核功能集成在内核中,内核过于庞大。
Linux内核引入内核模块机制。
通过动态加载内核模块,使得在运行过程中扩展内核的功能。
不需要的时候,卸载该内核模块。
内核模块
内核模块是一种没有经过链接,不能独立运行的目标文件,是在内核空间中运行的程序。
经过链接装载到内核里面成为内核的一部分,可以访问内核的公用符号(函数和变量)。
内核模块可以让操作系统内核在需要时载入和执行,在不需要时由操作系统卸载。
它们扩展了操作系统内核的功能却不需要重新启动系统。
如果没有内核模块,我们不得不一次又一次重新编译生成单内核操作系统的内核镜像来加入新的功能。
这还意味着一个臃肿的内核。
内核模块是如何被调入内核工作的?
当操作系统内核需要的扩展功能不存在时,内核模块管理守护进程kmod执行modprobe去加载内核模块。
modprobe遍历文件/lib/modules/version/modules.dep来判断是否有其它内核模块需要在该模块加载前被加载。
最后modprobe调用insmod先加载被依赖的模块,然后加载该被内核要求的模块。
内核模块是如何被调入内核工作的?
Insmod将调用init_module系统调用,传入参数
(Module.c)Sys_init_module系统调用检查权限后,并查找modules链表,验证模块未被链接。
然后分配一个module结构体变量描述该内核模块。
如果定义了模块的init方法,则执行init方法。
模块机制的特点:
减小内核映像尺寸,增加系统灵活性;
节省开发时间;修改内核,不必重新编译整个内核。
模块的目标代码一旦被链入内核,作用和静态链接的内核目标代码完全等价。
最简单的内核模块
任一个内核模块需要包含linux/module.h
初始化函数init_module(),在模块加载到内核时被调用。
init_module()向内核注册模块的功能,申请需要的资料等初始化工作。
卸载函数cleanup_module(),在内核模块被卸载时被调用,干一些收尾清理的工作,撤消任何初始化函数init_module()做的事,保证内核模块可以被安全的卸载。
printk()函数
printk函数在Linux内核中定义并且对模块可用,为内核提供日志功能,记录内核信息或用来给出警告。
与标准C库函数printf的行为相似。
每个printk()声明都会带一个优先级。
内核总共定义了八个优先级的宏,在linux/kernel.h中定义。
若你不指明优先级,DEFAULT_MESSAGE_LOGLEVEL这个默认优先级将被采用。
信息存在在内核消息缓冲区中,并被定时的添加到文件/var/log/messages,可直接查看,或者用命令dmesg查看。
在X-windows下的终端insmod一个模块,日志信息只会记录在日志文件中,而不在终端打印。
从内核Linux2.4之后,可以为模块的“初始化”和“卸载”函数起任意的名字。
不再必须使用init_module()和cleanup_module()的名字。
通过宏module_init()和module_exit()实现。
这些宏在linux/init.h中定义。
module_init(hello_2_init);
module_exit(hello_2_exit);
函数必须在宏的使用前定义,否则编译会报错。
关于__init和__exit宏
如果该模块被编译进内核,而不是动态加载,则宏__init的使用会在初始化完成后丢弃该函数并收回所占内存。
如果该模块被编译进内核,宏__exit将忽略“清理收尾”的函数。
这些宏在头文件linux/init.h定义,用来释放内核占用的内存。
例如启动时看到的信息“Freeingunusedkernelmemory:
236kfreed”,正是内核释放这些函数所占用空间时的打印信息。
内核模块证书和内核模块文档说明
2.4内核后,引入识别代码是否在GPL许可下发布的机制。
在使用非公开的源代码产品时会得到警告。
通过宏MODULE_LICENSE(“GPL”),设置模块遵守GPL证书,取消警告信息。
宏MODULE_DESCRIPTION()用来描述模块的用途。
宏MODULE_AUTHOR()用来声明模块的作者。
宏MODULE_SUPPORTED_DEVICE()声明模块支持的设备。
这些宏都在头文件linux/module.h定义。
使用这些宏只是用来提供识别信息。
准备工作
顺利编译并且加载第一个“helloworld”模块有时会比较困难。
保证系统具备正确的编译器、模块工具、以及其他必要工具。
内核目录Documentation/Changes列出了需要的工具版本。
用错误的工具版本建立一个内核(包括模块),可能导致一些奇怪复杂的问题。
通常芯片公司的SDK包会告诉你使用什么版本的交叉工具链去完成,并提供相应的工具链。
准备好系统平台所对应的内核源代码
配置并构建好平台对应的2.6内核源代码。
2.6内核使用kbuild构建系统配置编译,kbuild构建系统可用于编译自定义的内核模块。
编译过程首先会到内核源码目录下,读取顶层的Makefile文件,然后再编译模块源码,连接生成的内核模块后缀为.ko
在内核源代码目录下:
makemenuconfig,使得配置跟目标平台一致
make
编写内核模块的makefile
内核Makefile提供的obj-m表示对象文件(objectfiles)编译成可加载的内核模块
Hello-1.c的Makefile文件
obj-m+=hello-1.o
表明有一个模块要从目标文件hello-1.o建立,kbuild从该目标文件建立内核模块hello-1.ko。
编译内核模块的命令
make-C内核源代码路径M=模块所在路径modules
make-C/lib/modules/`uname-r`/buildM=$PWDmodules
改变目录到用-C选项提供的内核源码目录,make读取内核的makefile,并编译M所指定路径下的内核模块源代码。
内核模块的加载
2.6内核模块使用.ko的文件后缀(代替.o后缀)。
使用insmod./hello-1.ko命令加载该模块。
/proc/modules记录被加载的内核模块。
使用lsmod命令查看已经加载的模块
使用命令rmmodhello-1卸载模块
tail/var/log/messages
编写hello模块,包含初始化init_module和卸载函数cleanup_module
编写makefile文件
Obj-m+=hello-1.o
编译hello内核模块
make-C/lib/modules/`uname-r`/buildM=$PWDmodules
Uname–r给出当前系统内核的版本,使用短撇号,将其输出结果作为参数的一部分。
而build是符号链接,指向对应内核的源代码目录。
加载hello内核模块:
insmodhello-1.ko
查看加载信息:
tail/var/log/message
卸载hello内核模块rmmodhello-1
使用modprobe命令
修改/lib/module/`uname-r`/modules.dep文件,添加hello-1.ko内核模块路径和依赖关系
加载hello内核模块:
Modprobehello-1
卸载hello内核模块Modprobe–rhello-1
⏹修改hello内核模块的makefile
⏹添加all目标
⏹添加clean目标
obj-m+=hello-1.o
KERN_VER=$(shelluname-r)
KERN_DIR=/lib/modules/$(KERN_VER)/build
all:
$(MAKE)-C$(KERN_DIR)M=$(PWD)modules
clean:
rm-rf*.o
rm-rf*.mod.*
rm-rf*.ko
rm–rf.tmp_version
⏹直接通过make,编译内核模块
⏹多个文件构成的内核模块
⏹Makefile会帮我们完成编译和连接的工作。
⏹例如内核模块分两个文件start.cstop.c,则Makefile这样写:
obj-m+=test.o
test-objs:
=start.ostop.o
跟单个文件模块的编译方式一样,内核编译系统会将所有的目标文件连接为一个文件。
⏹内核模块的Makefile模块
mymodule-objs=file1.ofile2.o
obj-m+=mymodule.o
PWD=$(shellpwd)
KDIR=内核源代码路径
all:
$(MAKE)-C$(KDIR)M=$(PWD)modules
clean:
rm-rf.*.cmd*.o*.mod.c*.ko.tmp_versions
lsmod列出已经挂载的内核模块
lsmod是列出目前系统中已加载的模块的名称及大小等
效果跟通过less/proc/modules查看模块一样。
modinfo查看模块信息
modinfo可以查看模块的信息,通过查看模块信息来判定这个模块的用途。
modinfo模块名
modprobe挂载新模块以及新模块相依赖的模块
modprobe模块名,在挂载该内核模块的同时,这个模块所依赖的模块也被同时挂载。
modprobe还有其他用法,问他的“man”。
例如:
modprobe-l是列出内核中所有的模块,包括已挂载和未挂载的,读取的模块列表就位于/lib/modules/‘uname-r’目录中。
rmmod移除已挂载模块
用法:
rmmod模块名(不带后缀)
等同于:
modprobe-r模块名
insmod挂载模块
insmod需要给出模块所在目录的绝对路径,以及要带有模块文件名后缀(.o或.ko)
insmod/lib/modules/2.6.18/kernel/fs/vfat/vfat.ko
功能上没有modprobe强。
depmod创建模块依赖关系的列表
目前的的Linux发行版所用的内核是2.6x版本,是自动解决依赖关系。
depmod-a为所有列在/etc/modprobe.conf或/etc/modules.conf中的所有模块创建依赖关系,并且写入到modules.dep文件
depmod–e列出已挂载但不可用的模块
为2410开发板配置编译Linux内核
内核版本:
linux-2.6.24
交叉工具链:
cross-3.4.4.tar.gz
选择相应的配置时,有三种选择,它们分别代表的含义如下:
Y--将该功能编译进内核
N--不将该功能编译进内核
M--将该功能编译成模块,可以在需要时动态插入到内核中
makexconfig,使用鼠标就可以选择对应的选项。
makemenuconfig,则需要使用空格键进行选取。
如果有不明白的地方,按[shift]+?
的组合键来查看说明
主要配置选项
1.Codematurityleveloptions--->应该选择
[*]Promptfordevelopmentand/orincompletecode/drivers
这将会在设置界面中显示还在开发或者还没有完成的代码与驱动.因为有许多设备可能必需选择这个选项才能进行配置,实际上它是安全的。
Enableloadablemodulesupport打开可加载模块支持
2Generalsetup--->大部分保持默认
[*]Supportforpagingofanonymousmemory(swap)应该选择这个选项将使你的内核支持虚拟内存。
[*]SystemVIPC应该选择
为进程提供通信机制。
有些程序只有在选Y的情况下才能运行,这里一定要选。
[*]POSIXMessageQueues
POSIX的消息队列,它同样是一种IPC,应该选择
[*]Supportforhot-pluggabledevices支持热插拔的,若是为普通电脑配置内核,则最好选择
3Loadablemodulesupport--->
[*]Enableloadablemodulesupport应该选择
这个选项可以让你的内核支持模块。
一般一些不常用到的驱动或特性可以编译为模块以减少内核的体积。
[*]Moduleunloading应该选择
这个选项可以让你卸载不再使用的模块,如果不选的话你将不能卸载任何模块
[*]Forcedmoduleunloading
强行卸载模块,可以把正在使用中的模快卸载掉。
做内核开发或者驱动开发的时候,有一定的好处。
但一般用户不应该选择。
[*]Moduleversioningsupport模块版本支持
该选项可以使得系统支持其它版本的内核模块。
[]Sourcechecksumforallmodules否
这个功能是为了防止更改了内核模块的代码但忘记更改版本号而造成版本冲突。
[*]Automatickernelmoduleloading
在内核需要一些模块时,可以自动调用modprobe命令来加载需要的模块。
4Processortypeandfeatures--->
Processorfamily(Pentium-Pro)--->
选择cpu的类型
5Powermanagementoptions(ACPI,APM)--->
[*]PowerManagementsupport
支持高级电源管理(也就是平常我们说的软关机、系统休眠等)。
6Busoptions(PCI,PCMCIA,EISA,MCA,ISA)--->
[*]PCIsupportPCI支持。
当然必选
PCIaccessmode(Any)--->
PCI的存取方式,分三种,有透过BIOS或是直接存取跟任意,预设值Any
7Executablefileformats--->
[*]KernelsupportforELFbinaries一定要选择
8DeviceDrivers--->
MemoryTechnologyDevices(MTD)--->
存储设备层,大部分的flash芯片驱动都基于MTD。
用于嵌入式系統。
Blockdevices
Loopbackdevicesupport
可以将一个文件挂成一个文件系统。
mountiso文件
9filesystems--->
建议将根文件系统直接编译到内核中。
并且在这里选择系统支持的文件系统。
常见内核编译命令
makedep实际上读取配置过程生成的配置文件
MakezImage生成压缩的内核程序映像
makebzImage生成big的压缩的内核程序映像
makemodules生成相应的模块
makemodules_install把模块拷贝到需要的目录
makeclean清除之前所编译的执行文件及目的文件
Makedistclean清楚所有的执行文件、目标文件,以及其他一起中间文件,只剩下源代码文件。
内核配置命令:
makeconfig(基于文本的配置界面,不推荐使用)
makemenuconfig(基于文本菜单的配置界面)
makexconfig(要求QT被安装)
makegconfig(要求GTK+被安装)
Linux内核通过Kbuild构建系统来管理和配置
Kbuild构建系统的组成
Makefile定义Linux内核的编译规则,顶层目录的Makefile管理整个Linux内核的配置编译。
Kconfig描述配置菜单的文件
Documentation/kbuild目录详细描述
在Linux内核中增加程序的3项工作:
将编写的源代码复制到Linux内核源代码的相应目录.
在目录的Kconfig文件中增加新源代码对应项目的编译配置选项.
在目录的Makefile文件中增加对新源代码的编译条目.
Kbuild构建系统
Kconfig文件
arch/$(ARCH)/Kconfig文件是主Kconfig文件,主Kconfig文件调用其他目录的Kconfig文件。
这些Kconfig文件形成树状关系-->树状菜单。
script目录下提供不同的配置工具去读取Kconfig文件形成不同的配置方式。
如menuconfig目标使用mconf。
Documentation/kbuild/kconfig-language.txt提供了kconfig文件的书写语法。
S3C2410处理器的RTC设备驱动s3c2410-rtc.c
放在linux-2.6.24.7/drivers/har目录中
该目录的Kconfig文件中包含S3C2410_RTC的配置项目
configS2C2410_RTC
bool“S2C2410RTCDriver”
dependsonARCH_S2C2410
help
RTC(RealtimeClock)driverfortheclockinbuiltintotheSamsungS2C2410.Thiscanprovideperiodicinterruptratesfrom1Hzto64Hzforuserprograms,andwakeupfromAlarm.
菜单结构
菜单项在菜单结构中的位置定义:
menu“Networkdevicesupport”
dependsonNET
configNETDEVICES
...
endmenu
所有处于”menu”和”endmenu”之间的菜单项都会成为”Networkdcvicesupport”的菜单项或者子菜单.而且,所有子菜单选项都会继承父菜单的依赖关系
菜单项
内核配置选项对应Kconfig中的一个菜单项
“config”关键字定义新的配置选项,之后的几行定义了该配置选项的属性,配置选项的属性包括类型,数据范围,输入提示,依赖关系(及反向依赖关系),帮助信息和默认值等.
每个配置选项都必须指定类型,包括bool,tristate,string等
configMODVERSIONS
bool“Setversioninformationonallmodulesymbols”
dependsonMODULES
help
Usually,moduleshavetoberecompiledwheneveryouswitchtoanewkernel.
包含一个Kconfig文件
采用source命令读取一个Kconfig文件
Kconfig文件的路径是基于内核原代码的路径。
例如:
source“driver/char/ker/Kconfig”
目标定义
目标定义用来定义哪些内容要作为模块编译,哪些要编译并连接进内核.
例如:
obj-y+=foo.o
obj-y表示要由foo.c或者foo.s文件编译得到foo.o并连接进内核
obj-m表示该文件要作为模块编译.
根据.config文件的CONFIG_变量来决定文件的编译方式
obj–$(CONFIG_GEC_TEST)+=ker/
obj–$(CONFIG_GEC_TEST)+=test.o
多文件模块的定义
一个模块由多个文件组成,应采用模块名加-objs后缀或者-y后缀的形式来定义模块的组成文件
obj–$(CONFIG_EXT2_FS)+=ext2.o
ext2-objs:
=balloc.obitmap.o
实验:
往内核中添加你的代码
把你的内核模块代码ker目录拷贝到drivers/char/下
在ker目录下建立Kconfig,通过menu添加菜单,通过config添加菜单项
在char目录下的Kconfig文件中通过source包含自定义的ker的Kconfig文件
添加makefile
obj-$(CONFIG_GEC_TEST)+=test.o
修改上层makefile
obj-$(CONFIG_GEC_TEST)+=ker/
注明:
ker文件夹中包含编辑好的test.c文件
实验一、动态编译本机内核模块
/hello.c/
#include/*Neededbyallmodules*/
#include/*NeededforKERN_ALERT*/
#include
staticinttest_init(void)
{
printk("Helloworld1.\n");
/*Anon0returnmeansinit_modulefailed;modulecan'tbeloaded.*/
return0;
}
staticvoidtest_exit()
{
printk("Goodbyeworld1.\n");
}
module_init(test_init);
module_exit(test_exit);
/Makefile/
obj-m+=hello.o
PWD:
=$(shellpwd)
KERN_VER=$(shelluname-r)
KERN_DIR=/lib/modules/$(KERN_VER)/build
all:
$(MAKE)-C$(KERN_DIR)M=$(PWD)modules
clean:
rm–rf*.o*~core.depend.*.cmd*.ko*.mod.c.tmp_versions
编译生成的内核模块可以直接加载:
insmodhello.ko
卸载模块:
rmmodhello.ko
可以通过参看系统内核日志信息来查看模块的加载与卸载情况。
实验二、静态编译本机内核模块
将上述hello.c内核代码拷贝到内核源码drivers/char/下
在char目录下的Kconfig文件中通过config添加菜单项
configCHAR_TEST
tristate"hello_world_testing"
dependson