Linux系统按照程序运行空间(或权限)分用户空间和内核空间,内核空间运行linux内核程序代码。Linux内核代码属于单内核(monolithic kernel),其优点是允许效率高,所有的内核代码都集成一体,代码的耦合度高。然而其缺点就是其优点导致,可扩展性和维护性差,比较麻烦。LKM模块机制解决了linux内核的缺陷,其提供了内核可以动态接入和卸载一些程序代码,这类代码称之为模块。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。总之,模块是一个为内核(从某种意义上来说,内核也是一个模块)或其他内核模块提供使用功能的代码块。若一个模块程序用于管理某些硬件设备的,如网卡,键盘、摄像头等,这种模块称为设备驱动模块。
1、 装入的内核模块和其他内核部分一样,具有相同的访问权限,因此,差的内核模块会导致系统崩溃;
2、 为了使内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改这些符号表;
3、 有些模块要求利用其他模块的功能,因此,内核要维护模块之间的依赖性。
4、 内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源;
5、 内核版本和模块版本的不兼容,也可能导致系统崩溃,因此,严格的版本检查是必需的。
尽管内核模块的引入同时也带来不少问题,但是模块机制确实是扩充内核功能一种行之有效的方法,也是在内核级进行编程的有效途径。
应用程序运行在OS环境中,而模块程序应用在kernel环境中。
功能项 |
OS |
Kernel |
关系 |
OS必然包含Kernel方能运行 |
Kernel可以成为OS一部分,没有OS时,Kernel能独立运行。 |
目标 |
服务于应用程序 |
服务于上层程序(基本是OS,也可直接服务应用程序,如路由器设备的系统代码) |
功能 |
为应用程序提供良好的运行机制及环境,同时提供各种统一的资源接口。 |
以CPU资源为核心,进行各种任务调度和通讯机制,并且协同各种模块一道服务于上层(OS)。 |
1. 因为模块不能像应用程序那样在系统平台下自由运行和结束,而是被系统的内核调用运行和释放。所以模块必须提供两个函数给内核调用init_module()和cleanup_module()。
2. 必须提供一种途径向内核告知自己是什么模块,MODULE_LICENSE()宏函数用来声明自己是遵循何种规则的模块。
3.模块初始化可以设定或模块之间调用,可使用module_param()和EXPORT_SYMBOL_GPL()。
4.为了便于使用者方便使用该模块,可以提供一些模块使用信息说明,如:MODULE_DESCRIPTION()、MODULE_AUTHOR()和MODULE_DEVICE_TABLE()、MODULE_VERSION()、MODULE_ALIAS()分别声明模块的描述、作者、设备表、版本、别名。
从上述描述看,我们可以确定:init_module()、cleanup_module()和MODULE_LICENSE()必须的,其它都是根据需要可选择的。
另外,module_init()和module_exit()两个函数可以替换inid_module()和cleanup_module()函数,是因为系统内核便于用户多样化命名模块的挂载和卸载函数,当然用户的挂载函数名和卸载函数名必须以参数的形式分别填入module_init()和module_exit()中,方能替代init_module()和cleanup_module()这两函数。
//引用头文件
#include "linux/init.h"
#include "linux/module.h"
//声明模块支持协议
MODULE_LICENSE("GPL");
//模块加载程序,被insmod命令调用
int __init init_module()
{
//此处你可写正在挂载代码
printk("Thismodule was mounted by the kernel!\n");//用dmesg命令可以查看该信息
}
//模块卸载程序,被rmmod命令调用
void __exit cleanup_module()
{
//此处你写正在卸载代码
printk("Thismodule was unmounted by the kernel!\n");//用dmesg命令可查看该信息
}
__init关键字表示后边的函数为初始化程序,该函数一旦执行完毕,系统就把该函数代码从内存中清理出来,因为初始化程序在整个模块生命周期中仅仅运行一次,所以一旦运行完毕就没有保留价值。
__exit关键字表示后边的函数为卸载函数,功能与__init一样。
上面是最简单的模块程序框架,注意模块没有向应用程序那样有主函数main(),而是由许多服务子程序堆积起来,被别的模块或应用程序调用,所以模块是一种服务程序,以内核一道服务与系统。
//头文件引用
#include
#include
//模块协议支持声明
MODULE_LICENSE("GPL");
//作者声明
MODULE_AUTHOR("wozon.zhou mytel:119");
//模块描述,可用modinfo命令查看
MODULE_DESCRIPTION("The module is usedto test!");
//支持设备声明,可用modinfo命令查看
MODULE_SUPPORTED_DEVICE("NOdevice!");
//模块版本声明,可用modinfo命令查看
MODULE_VERSION("1.0V");
//模块参数声明
static int i_int = 0;
static char *name ="wozon";
module_param(i_int,int,S_IRUGO);
module_param(name,charp,S_IRUGO);
//模块参数表述,可用modinfo命令查看
MODULE_PARM_DESC(i_int,"input aint");
MODULE_PARM_DESC(name,"input astring");
//加法函数
int Amodule_add(int a,int b)
{
return a + b;
}
EXPORT_SYMBOL_GPL(Amodule_add);//函数引出声明
//模块挂载函数
static int __init Amodule_init(void)
{
printk("Hellothe p module was installed!\n");
printk("Thei_int is %d, and the name is %s\n",i_int,name);
return0;
}
//卸载函数
static void __exit Amodule_exit(void)
{
printk("Hellop base module was removed!\n");
}
//绑定装载和卸载函数
module_init(Amodule_init);//被insmod命令调用
module_exit(Amodule_exit);//被rmmod命令调用
模块参数module_param(参数名称,参数类型,参数权限),为模块定义一个参数。在装载模块时,用户可以向模块传递参数,形式为"insmode(或modprobe) 模块名 参数名=参数值",如果不传递,参数将使用模块内定义的缺省值。
参数类型:
名称 |
说明 |
名称 |
说明 |
byte |
8bits |
long |
有符号64bits |
short |
有符号16bits |
ulong |
无符号64bits |
ushort |
无符号16bits |
charp |
字符指针 |
int |
有符号32bits |
bool |
布尔值 |
uint |
无符号32bits |
invbool |
反布尔值 |
参数选项:
标志 |
含义 |
S_IRUSR值为4 |
用户可读写 |
S_IWUSR值为2 |
用户可写 |
S_IXUSR值为1 |
用户可执行 |
S_IRWXU值为7 |
用户可读写执行 |
S_IRGRP值为4 |
组可读 |
S_IWGRP值为2 |
组可写 |
S_IXGRP值为1 |
组可执行 |
S_IRWXG值为7 |
组可读写执行 |
S_IROTH值为4 |
其他人可读 |
S_IWOTH值为2 |
其他人可写 |
S_IXOTH值为1 |
其他人可执行 |
S_IRWXO值为7 |
其他人可读写执行 |
S_IRUGO |
用户组其他人可读 |
|
|
#To display info during Ccompiling
$(warning KERNELRELEASE=$(KERNELRELEASE))
#To check the KERNELRELEASE enviroment value
ifeq ($(KERNELRELEASE),)
#It's NULL, so to set the enviroment value
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR)M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR)M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend.*.cmd *.ko *.mod.c .tmp_versions Module* modules*
.PHONY: modules modules_install clean
#To create obj file
else
obj-m := modulename.o
endif
内核模块编译需要内核源码支持,对内核源码有如下要求:
1、 必须编译过,方能提供必要的.O(或.KO)动态库文件和头文件的支持;
2、 注意版本与自己编写模块代码要一致。
用户的Makefile在一次编译中,一共被调用三次:
1、 第一次,是make命令调用,设置内核源码路径,同时下达modules工作命令。
2、 第二次是有内核的modules调用该Makefile,完成将源码编译成.o文件。
3、 第三次还是内核的modules调用该MakeFile文件,完成将.o文件编译成.ko文件。
目标输出文件obj-m := modulename.o,表示动态模块,如果为静态的,则为obj-y := modulename.o。
.PHONY: modulesmodules_install clean为伪目标定义,就是定义modules、modules_install和clean是命令,而不是文件。
obj-m := modulename.o和obj-y := modulename.o仅仅编译单个文件的当个模块。
多个单文件和单模块编译则:obj-m:=xxx.o yyy.o zzz.o就可以编译三个模块了。
多个文件编译成一个模块:
obj-m :=xxx.o
xxx-objs :=a.ob.o c.o
或多文件及多模块:
方案一:
obj-m :=A.o
A-objs:=ab.o ac.o ad.o
obj-m +=B.o
B-objs:=bb.o bc.o bd.o
方案二:
obj-m :=A.o B.o
A-objs:=ab.o ac.o ad.o
B-objs:=bb.o bc.o bd.o
编译后的.ko模块文件,如何运行?
首先,模块是系统内核的一部分,所以只有root权限方能运行,使用sudo命令可以进入。
1、 用insmod命令加载:insmod xxx.ko,内核会调用xxx.ko模块中的挂载程序,如果挂载程序有printk命令,可以使用dmesg命令查看。xxx.ko模块就被加载到内核中,可以用lsmod查看xxx模块信息。
2、 用rmmod命令可以卸载模块:rmmod xxx.ko或者rmmod xxx卸载,内核会调用xxx.ko模块中卸载程序,这时候,xxx模块被内核卸载。
3、 可以使用modinfo查看模块信息:modinfo xxx.ko,其会列出xxx模块的相关信息。
注:
lsmod命令列出的内容,其实就是列出/proc/modules文件的内容。
Dmesg命令列出的内容,其实就是列出/var/log/messages文件内容。
另外一个命令modprobe命令:
modprobe [-acdlrtvV][--help][模块文件][符号名称 = 符号值]
modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。
依赖关系是通过读取/lib/modules/2.6.xx/modules.dep得到的。而该文件是通过depmod 所建立。