1.6 内核模块
1.6.1 内核模块基础知识
内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。总之,模块是一个为内核(从某种意义上来说,内核也是一个模块)或其他内核模块提供使用功能的代码块。
1,内核模块的特点
(1)模块本身并不被编译进内核文件(zImage或者bzImage)
(2)可以根据需求,在内核运行期间动态的安装或者卸载。
2,安装/卸载内核模块
(1)安装insmod
例:insmod dnw_usb.ko
(2)卸载rmmod
例:rmmod dnw_usb.ko
(3)查看 lsmod
例:lsmod
1.6.2 内核模块的设计
1,内核模块范例
#include<linux/init.h>
#include<linux/module.h>
static int hello_init(){
printk(KERN_WARNING"hello world!\n");
return 0;
}
static int hello_exit(){
printk(KERN_WARNING"hell exit!\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
内核通过printk()输出的信息具有日志级别,日志级别是通过在printk()输出的字符串前加一个带尖括号的整数来控制的,如printk("<6>Hello, world!\n");。内核中共提供了八种不同的日志级别,在 linux/kernel.h 中有相应的宏对应。
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
KERN_EMERG
用于突发性事件的消息,通常在系统崩溃之前报告此类消息。
KERN_ALERT
在需要立即操作的情况下使用此消息。
KERN_CRIT
用于临界条件下,通常遇到严重的硬软件错误时使用此消息。
KERN_ERR
用于报告错误条件;设备驱动经常使用KERN_ERR报告硬件难题。
KERN_WARNING
是关于问题状况的警告,一般这些状况不会引起系统的严重问题。
KERN_NOTICE
该级别较为普通,但仍然值得注意。许多与安全性相关的情况会在这个级别被报告。
KERN_INFO
信息消息。许多驱动程序在启动时刻用它来输出获得的硬件信息。
KERN_DEBUG
用于输出调试信息。
2.编写Makefile
obj-m:=helloworld.o
KDIR:=/home/win/Tiny6410/linux-tiny6410
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux-ARCH=arm
clean:
rm -f *.o,*.ko,*.order,*.symvers
obj-m := helloworld.o为固定格式,指明内核模块的名字,如该模块有多个文件组成则须在下面加一行helloworld-objs := file1.o file2.o;KDIR := /home/win/Tiny6410/linux-tiny6410指明内核代码位置;make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm,-C进入到内核代码中,M指明内核模块的位置。
1.6.3 内核模块可选信息
1,模块申明
(1)申明该模块遵守的许可证协议,如:“GPL”,“GPL v2”等
MODULE_LICENSE("GPL")
(2)申明模块的作者
MODULE_AUTHOR("JON")
(3)申明模块的功能
MODULE_DESCRIPTION("描述模块的功能")
(4)申明模块的版本
MODULE_VERSION("V1.0")
2,模块参数
在应用程序中int main(int argc, char** argv)。argc表示命令行输入的参数个数,argv中保存输入的参数。通过宏module_param指定保存模块参数的变量。模块参数用于在加载模块时传递参数给模块。
module_param(name,type,perm);
说明:
name:变量的名称
type:变量类型,bool:布尔型 int:整型 charp:字符串型
perm:是访问权限。S_IRUGO:读权限 S_IWUSR:写权限
3,符号导出
如果内核模块中的变量或函数等要提供给其他内核模块使用,必须使用宏EXPORT_SYMBOL(或者EXPORT_SYMBOL_GPL)将符号导出。
EXPORT_SYMBOL(符号名)
EXPORT_SYMBOL_GPL(符号名)
说明:其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块。
//helloworld.c
#include<linux/init.h>
#include<linux/module.h>
extern int add(int a,int b);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello world");
MODULE_VERSION("V1.0");
int a=3;
module_param(a,int,S_IRUGO|S_IWUSR);
static int hello_init(){
printk(KERN_WARNING"hello world!\n");
printk("a=%d\n",a);
return 0;
}
static int hello_exit(){
int i;
i=add(3,4);
printk("3+4=%d\n");
printk(KERN_WARNING"hello exit!\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
//add.c
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello world");
MODULE_VERSION("V1.0");
int add(int a,int b){
return (a+b);
}
static int add_init(){
return 0;
}
static int add_exit(){
return 0;
}
EXPORT_SYMBOL(add);
module_init(add_init);
module_exit(add_exit);