摘要:
本文回答了为什么需要内核模块动态加载LKM,通过一个简单实例描述了模块加载、卸载过程,接着以实例讲述模块间调用问题及应用程序怎样调用模块内函数,最后给出一些关于模块学习资料。
一、动态可加载内核模块(Loadable Kernel Module,LKM)
1.1 为什么要LKM
要回答这点,有必要先了解下单内核与微内核的优缺点。单内核与微内核是操作系统内核设计的两大阵营。
(1)微内核
在微内核中,所有的服务器(微内核的功能被划分为独立的过程,称服务器[1])运行在各自的地址空间互相独立,只有强烈要求特权服务的服务器才运行在特权模式下(比如进程间通信、调度、基本的输入/输出、内存管理),其他服务器运行在用户空间,各个服务器之间采用进程间通信(IPC)机制。优点:安全可靠(服务间独立)、扩展性好;缺点:效率低下(IPC机制开销比函数调用多)。
(2)单内核
单内核作为一个大过程实现,同时运行在一个单独的地址空间。优点:简单、高性能(内核间通信微不足道、内核可以直接调用函数);缺点:可扩展性和可维护性差一点[2]。
Linux属于单内核,为了弥补单内核扩展性与维护性差的缺点,Linux引入动态可加载内核模块,模块可以在系统运行期间加载到内核或从内核卸载。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行(属于内核编程,不能访问C库等)。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能[2]。
1.2 一个简单例子
模块代码可以放在内核源代码树中,也可以放在内核代码外(区别在于构建过程),这里选择放在内核代码外,以书[1]的例子为例。
(1)模块module_hello
- #include <linux/init.h> //包含了宏_init和_exit,允许释放内核占用的内存
- #include <linux/module.h> //所有模块都要使用头文件module.h
- #include <linux/kernel.h> //常用的内核函数
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Shakespeare");
- /***模块的初始化函数,这里可以包含诸如要编译的代码、初始化数据结构等内容***/
- static int hello_init(void)
- {
- printk(KERN_ALERT "I bear a charmed life.\n");
- return 0;
- }
- /***模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作***/
- static void hello_exit(void)
- {
- printk(KERN_ALERT "Out,out,brief candle!\n");
- }
- module_init(hello_init);//对于内置模块,内核在引导时调用该入口点;对于可加载模块,模块插入时调用hello_init
- module_exit(hello_exit);//对于内置模块,内核在引导时调用该入口点;对于可加载模块,模块插入时调用hello_exit
(2)Makefile文件
- # Makefile2.6
- obj-m += module_hello.o #产生module_hello模块的目标文件
- CURRENT_PATH := $(shell pwd) #模块所在的当前路径
- LINUX_KERNEL := $(shell uname -r) #Linux内核源代码的当前版本
- LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径
- all:
- make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
- clean:
- make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理
(3)编译模块
用make命令编译,编译完会产生若干新的文件:module_hello.ko module_hello.mod.c module_hello.mod.o module_hello.o modules.order。其中module_hello.ko即为所需。
(4)插入模块
用命令sudo insmod module_hello.ko插入模块,用命令dmesg可以查看内核环缓冲(可以用sudo dmesg -c 完成打印显示后清除环缓冲内的内容),结果如下:
- jelline@jelline:~/project/module_test$ dmesg
- [36478.340651] I bear a charmed life.
(5)查看模块
用命令
lsmod | grep module_hello或者
cat /proc/kallsyms | grep module_hello查看被加入的模块信息,如下:
(6)卸载模块
用命令
sudo rmmod module_hello卸载模块,dmesg查看内核环缓冲如下:
- [36478.340651] I bear a charmed life.
- [36808.842565] Out,out,brief candle!
二、模块间函数调用
2.1 修改modual_hello
在上述模块modual_hello加入函数printStr,并且在模块module_call调用该函数。printStr函数源代码如下:
- void printStr(const char *s)
- {
-
- printk(s);
- }
- EXPORT_SYMBOL_GPL(printStr);
模块module_call源代码如下:
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Jelline");
- extern void printStr(char *s); //module_hello.ko
- static int hello_init(void)
- {
- printStr("Begin module_call.\n");
- return 0;
- }
- static void hello_exit(void)
- {
- printStr("End module_call.\n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
2.2 错误Unknown symbol in module
编译好模块module_call,插入时遇到如下错误:
- insmod: error inserting 'module_call.ko': -1 Unknown symbol in module
解决方法:
(1)拷贝文件Module.symvers
将模块module_hello编译产生的文件Module.symvers拷贝到模块module_call目录下,重新编译,插入成功。
(2)将模块放在同一目录下
将module_hello.c、module_call.c放在同个目录,并修改Makefile中的obj-m语句,如下:
obj-m += module_hello.o module_call.o
(3)其他更好的方法
三、应用程序调用模块内函数
应用程序(用户空间)无法直接调用模块中的函数(内核空间),用户空间访问内核空间只能通过异常、陷入、系统调用。关于这一点,可以阅读下面这一篇博文:
《在linux中 应用程序如何调用模块内的函数》
四、进一步了解
4.1 模块描述信息
- MODULE_AUTOR("作者信息");
- MODULE_DESCRIPTION("模块描述信息");
- MODULE_VERSION("版本信息");
- MODULE_ALIAS("别名信息");
- MODULE_DEVICE_TABLE("设备表信息");
4.2 导出符号表
- EXPORT_SYMBOL() ;//对全部内核代码公开
- EXPORT_SYMBOL_GPL();//只适合GPL许可的模块进行调用
4.3 模块参数
模块参数是模块内部的全局变量,在加载模块时(insmod/modprobe),可以向模块传递的参数值,举例如下:
- static char *interface; //模块内部定义变量
- module_param(interface, charp, 0644); //module_param(name, type, perm)
- sodu insmod module_myirq.ko interface=eth0 //插入模块
除此之外,还有:
- module_param_named(name, variable, type, perm); //模块外部参数名称不同于模块内部变量名称
- module_param_string(name, string, len, perm);//内核直接拷贝字符串到指定字符数组string
- module_param_array(name, type, nump, perm);
- module_param_array_named(name, array, type, nump, perm);
- MODULE_PARM_DESC(); //描述参数
更多信息可查看。
关于模块,可以进一步阅读博文《》,尤其是博文《Linux 可加载内核模块剖析》,从系统角度出发,全面、深入浅出剖析了动态内核加载模块。
参考资料:
[1]Robert Love著.陈莉君译.Linux内核设计与实现(第二版)[M].北京:机械工业出版社.2006.1
[2]博文《内核模块编程之入门(一)-话说模块》
阅读(1588) | 评论(0) | 转发(0) |