#ifndef __KERNEL__ # define __KERNEL__ #endif #ifndef MODULE # define MODULE #endif #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE("GPL"); static int year=2013; MODULE_PARM(year,"i"); int hello_init() { printk(KERN_WARNING"Hello World %d! ",year); return 0; } void hello_exit() { printk("Hello Exit! "); } module_init(hello_init); module_exit(hello_exit);
1.在包含任何头文件前,必须先预定义符号__KERNEL__,这个符号用于控制选择头文件的哪一部分。
2.另一个很重要的符号就是MODULE,必须在包含<linux/module.h>前定义这个符号,它用于告诉头文件,这是一个模块,如果编译进内核,必须去掉该定义。
3.模块加载函数(必需):安装模块时被系统自动调用的函数,通过module_init宏来指定。
4.模块卸载函数(必需):卸载模块时被系统自动调用的函数,通过module_exit宏来指定。
5.许可证申明(可选):宏MODULE_LICENSE被用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有"GPL","GPL v2","GPL and additional rights","Dual BSD/GPL","Dual MPL/GPL"和“Proprietary"。
6.模块参数(可选):通过宏MODULE_PARM指定模块参数,模块参数用于在加载模块时传递参数给它。
MODULE_PARM(name,type)有两个参数,name是模块参数的名称,type是这个参数的类型,类型包括以下几种:
b:比特型 h:短整型 i:整型 l:长整型 s:字符串型
在传递字符串型的参数时,这个模块参数需要在模块中用char* 来声明,系统会自动为其分配内存空间。
例如:
int a = 3;
char* st;
MODULE_PARM(a,"i");
MODULE_PARM(st,"s");
7.模块的编译工作由gcc -c 命令来完成。例如:
#gcc -c -I/usr/src/linux-headers-2.6.32-48/include hello.c
8.加载 insmod (insmod hello.o)
9.卸载 rmmod (rmmod hell)
10.查看 lsmod
11.加载 modprobe
modprobe如同insmod,也是加载一个模块到内核。它的不同之处在于它会查看要加载的模块,看它是否还依赖于其他模块,如果是modprobe找到这些模块,把他们先加载到内核。
12.为了确定模块是否可以被安全的卸载了,系统为每个模块保留了一个使用计数(lsmod可查看),用于记录正在使用该模块的用户数,只有当使用计数=0时,模块才可以被卸载。
以下3个宏在内核中用来维护使用计数:
MOD_INC_USE_COUNT:模块计数加1
MOD_DEC_USE_COUNT:模块计数减1
MOD_IN_USE:模块计数非0时返回真
内核模块开发过程中需特别注意以下几点:
1.在使用gcc编译模块时使用-c编译选项
2.在gcc编译选项中定义宏:_DMODULE和 -D__KERENL__
或直接在源文件中定义这两个宏:
#define MODULE
#define __KERNEL__
3.在使用gcc编译内核模块时,需要通过增加编译选项:-I/XXX/include 来指定内核源代码的头文件目录,并且还要保证内核源代码必须是配置过(make menuconfig),make dep过的。
注:XXX代表内核源代码的绝对路径,如:/usr/src/linux-headers-2.6.32-48/
4.版本不匹配
内核模板的版本是由其所依赖的内核源代码版本所决定的,位于内核源代码所处的顶层Makefile中,如:
VERSION = 3
PATCHLEVEL = 9
SUBLEVEL = 2
EXTRAVERSION =
当此版本与正在运行的内核版本(可通过uname -r 查询)不一致时,内核模块将无法插入内核。
解决办法:
1.使用insmod -f 强行插入
2.修改内核源代码顶层Makefile中的版本信息来与uname -r查看到的一致。