内核模块的特点
linux内核模块是一个编译好的,具有特定格式的独立目标文件,用户可通过系统提供的一组与模块相关的命令将内核模块加载进内核,当内核模块被加载后,它具有一下特点:
1.与内核一起运行在相同的内核态和内核地址空间
2.运行时和内核拥有一样的特权级
3.可访问内核中的各种数据结构
被加载到内核的内核模块代码和静态编译进内核的代码没有区别,内核模块与内核中其他模块之间的交互只需要函数调用。当用户不在需要某功能模块时,可将其从内核中卸载,配置灵活。
加载模块的管理
linux内核管理内核模块主要有两项任务,内核符号表管理,维护内核模块引用计数。
1.内核将资源等级在符号表中,内核模块被加载后,模块可通过符号表使用内核中的资源,新模块加载到内核时,系统将新模块提供的符号加进
符号表中,这样新加载模块就可访问一家在模块提供的资源;在卸载一个模块时,系统释放分配给该模块所用系统资源,同事将该模块提供的符号从符号
表中删除。
2.引用计数是用来管理内核模块之间依赖性的。内核模块在加载后都在同一地址空间,内核模块之间可相互引用各自到处的符号,也就产生了依赖。如果A模块需要用到B模块到处的符号,而B模块没有被加载到内核,A模块的加载就会出错;同样,一个内核模块如果有其他内核模块引用它导出的符号,内核也不允许该模块被卸载。如果一个模块被依赖,它的引用技术就会增加,当依赖减少时,相应的引用计数也会减少,一个内核模块只有在引用计数为0的时候才能被卸载。
如何编写一个内核模块
这里介绍的内核模块编程以2.6内核版本为例,由于内核模块在内核空间运行,无法与c库函数链接,因此它的编写受到内核编程的限制,如不能使用浮点计算。
一个典型的内核模块结构应包含一下几个部分:
1.头文件声明
#include<linux/init.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
init.h中包含模块初始化和清理函数的定义;
module.h包含加载模块需要的函数和符号定义;
moduleparam.h允许模块加载时用户传递参数。
2.模块许可声明
MODULE_LICENSE("GPL");
模块需要通过MODULE_LICENSE宏声明此模块的许可证。从/usr/scr/linux_xxx/include/linux/module.h文件可看到被内核接受的许可证
有GPL、GPL v2、GPL and additional rights、Dual BSD/GPL等
3.初始化和清理函数声明
staticint xxx_init()
{
xxx;
return0;
}
staticvoid xxx_exit()
{
xxx;
}
module_init(xxx_init);
module_exit(xxx_exit);
内核模块必须使用宏module_init和module_exit去注册初始化与清理函数,而且初始化与清理函数必须在注册之前定义。初始化与清理函数是配对使用的,例如module_init()中申请了一个资源,那么module_exit()中就应该释放这个资源。
总体来说我们需要做一下几个工作:
1.查看和下载当前版本的Linux源代码
如果当前运行的内核版本与你编译链接的头文件版本不一致,加载内核模块时会遇到insmod: error inserting 'xxx.o' :-l invalid module format这样的错误
可以使用uname -a查看版本,在使用apt-get install linux-source 下载源代码包。
#include<linux/init.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
MODULE_LICENSE("GPL");
/* theinit function*/
inthello_init()
{
printk(" hello world !\n");
printk(" I have runing in akerner");
return 1;
}
/* thedistory function*/
inthello_exit()
{
printk("I will shut down myself inkernerl mod \n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
2.编写makefile
在与模块程序相同的目录下编写模块程序的Makefile文件
obj-m := hello.o
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
说明:
1.执行该Makefile文件时将跑到 KERNELDIR下调用make,即/lib/modules/$(shell uname -r) /build下必须有一个Makefiule文件;
2.PWD指定要编译的模块程序源文件所在的目录,$(shell pwd) 表示当前目录,最终要生成的目标是modules
几点说明
关于printfk
1.printk是内核提供的打印函数。printf是glibc提供的函数,linux内核函数是不能依赖于任何程序库的;
2.printk()打印的信息一般在/var/log/messages文件里;
3.可以使用dmesg命令查看,如果只想显示最后n行,可以用dmesg | tail -n。
关于模块的命令
1.加载
insmod/path/modulename 加载后在/sys/module中就可以看到模块了
2.卸载
rmmod/path/modulename 卸载后在/sys/module中模块移除
3.查看系统以加载模块
lsmod
4.查看模块信息
modinfo/path/modulename