Linux内核模块开发
内核模块
一种机制能让内核文件(zImage或者bzImage)本身并不包含某组件,而是在该组件需要被使用的时候,再动态的增加到内核。
内核模块特点:
- 内核本身并不被编译到内核文件;
- 可以根据需求,在内核运行期间动态的安装或卸载;
内核模块程序范例
#include<linux/init.h> #include<linux/module.h> static int hello_int(void) { prinfk(KERN_WARNING"Hello, world!\n"); return 0; } static void hello_exit(void) { prinftk(KERN_INFO"Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit);
- 模块加载函数(必需)
安装模块时被系统自动调用的函数,通过module_init
宏来指定。 - 模块卸载函数(必需)
卸载模块时被系统自动调用的函数,通过module_exit
宏来指定。
内核模块编译
使用makefile.
- 内核模块由一个源文件组成,该如何编写makefile?
ifneq ($(KERNELRELEASE),) obj-m := hello.o else KDIR := /lib/modules/2.6.18-53.e15/build all: make -C $(KDIR) M=$(PWD) modules # -C进入该目录下,执行该目录下的makefile文件 M表示当前代码所在路径 modules表示模块 clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif
- 内核模块由多个源文件组成,该如何编写makefile?
ifneq ($(KERNELRELEASE),) obj-m := mymodule.o # 最终生成的执行文件名称 mymodule-objs := main.o add.o # 所有有关的源文件 else KDIR := /lib/modules/2.6.18-53.e15/build all: make -C $(KDIR) M=$(PWD) modules # -C进入该目录下,执行该目录下的makefile文件 M表示当前代码所在路径 modules表示模块 clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif
内核模块安装与卸载
- 加载 insmod
- 卸载 rmmod
- 查看 lsmod
- 加载 modprobe: 如果insmod,也是加载一个模块到内核。但是它更加强大,会自动寻找相关内容,自动加载。
内核模块可选参数
#include<linux/init.h> #include<linux/module.h> MODULE_LICENSE("GPL") //遵循的开源协议 MODULE_AUTHOR("Your name")//作者申明 MODULE_DESCRIPTION("Hello World Module")//模块描述 MODULE_ALIAS("a simplest module")//模块别名 static int hello_int(void) { prinfk(KERN_WARNING"Hello, world!\n"); return 0; } static void hello_exit(void) { prinftk(KERN_INFO"Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit);
module_param(name,type,perm)
:模块参数->指定模块参数,模块参数用于加载模块时传递参数给模块。
- name 是模块参数的名称
- type 是这个参数的类型 ->常见类型:bool 布尔型, int 整型, charp 字符串型
- perm 是模块参数的访问类型 -> S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限;S_IWUSR:允许root用户修改/sys/module中出现的该参数;
例如:int a = 3; module_param(a,int,S_IRUGO);
或者char *st; module_param(st,charp,S_IRUGO);
具体例子:
#include<linux/init.h> #include<linux/module.h> MODULE_LICENSE("GPL") //遵循的开源协议 MODULE_AUTHOR("Your name")//作者申明 MODULE_DESCRIPTION("Hello World Module")//模块描述 MODULE_ALIAS("a simplest module")//模块别名 static char *name = "dengbo sun"; static int age = 30; module_param(age,int,S_IRUGO); module_param(name,charp,S_IRUGO); static int hello_int(void) { prinfk(KERN_WARNING"Hello, world!\n"); prinfk(KERN_EMERG"Name:%s\n",name); prinfk(KERN_EMERG"Age:%d\n",age); return 0; } static void hello_exit(void) { prinftk(KERN_INFO"Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit);
$ insmod hello.ko age = 32
内核符号导出
/proc/kallsyms
中记录了所有导出的符号名称和地址
为什么需要内核符号导出?方便其他模块使用该模块的函数
内核符号导出方式
EXPORT_SYMBO(func name);
常见问题
- 版本不匹配:
使用uname -r
查看当前内核版本。
解决办法:- 使用
modprobe --force-modversion
强行插入内核. - 找到相符合的内核版本.
- 使用
总结
与应用程序区别在于,应用程序从main函数执行完成就没了。内核模块则一直都在,除非卸载。
内核打印
printfk // 只能在内核内使用,并且有优先级 /*优先级*/ KERN_EMERG ->"0" KERN_ALERT ->"1" KERN_CRIT ->"2" KERN_ERR ->"3" KERN_WARNING ->"4" KERN_NOTICE ->"5" KERN_INFO ->"6" KERN_DEBUG ->"7" printf // 只能在应用程序内使用