Chinaunix首页 | 论坛 | 博客
  • 博客访问: 465680
  • 博文数量: 85
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 32
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-13 13:49
文章分类

全部博文(85)

文章存档

2018年(1)

2014年(40)

2013年(44)

分类: LINUX

2014-01-23 22:25:56

原文地址:内核模块动态加载LKM实例 作者:Jelline

摘要:

    本文回答了为什么需要内核模块动态加载LKM,通过一个简单实例描述了模块加载、卸载过程,接着以实例讲述模块间调用问题及应用程序怎样调用模块内函数,最后给出一些关于模块学习资料。 


 一、动态可加载内核模块(Loadable Kernel Module,LKM) 

 1.1 为什么要LKM 

    要回答这点,有必要先了解下单内核与微内核的优缺点。单内核与微内核是操作系统内核设计的两大阵营。

 (1)微内核 

    在微内核中,所有的服务器(微内核的功能被划分为独立的过程,称服务器[1])运行在各自的地址空间互相独立,只有强烈要求特权服务的服务器才运行在特权模式下(比如进程间通信、调度、基本的输入/输出、内存管理),其他服务器运行在用户空间,各个服务器之间采用进程间通信(IPC)机制。优点:安全可靠(服务间独立)、扩展性好;缺点:效率低下(IPC机制开销比函数调用多)。 

 (2)单内核 

    单内核作为一个大过程实现,同时运行在一个单独的地址空间。优点:简单、高性能(内核间通信微不足道、内核可以直接调用函数);缺点:可扩展性和可维护性差一点[2]。 

    Linux属于单内核,为了弥补单内核扩展性与维护性差的缺点,Linux引入动态可加载内核模块,模块可以在系统运行期间加载到内核或从内核卸载。 模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行(属于内核编程,不能访问C库等)。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能[2]。 

1.2 一个简单例子

    模块代码可以放在内核源代码树中,也可以放在内核代码外(区别在于构建过程),这里选择放在内核代码外,以书[1]的例子为例。 

(1)模块module_hello 

  1. #include <linux/init.h>  //包含了宏_init和_exit,允许释放内核占用的内存
  2. #include <linux/module.h> //所有模块都要使用头文件module.h
  3. #include <linux/kernel.h> //常用的内核函数

  4. MODULE_LICENSE("GPL");
  5. MODULE_AUTHOR("Shakespeare");

  6. /***模块的初始化函数,这里可以包含诸如要编译的代码、初始化数据结构等内容***/
  7. static int hello_init(void)
  8. {
  9.     printk(KERN_ALERT "I bear a charmed life.\n");
  10.     return 0;
  11. }

  12. /***模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作***/
  13. static void hello_exit(void)
  14. {
  15.     printk(KERN_ALERT "Out,out,brief candle!\n");
  16. }

  17. module_init(hello_init);//对于内置模块,内核在引导时调用该入口点;对于可加载模块,模块插入时调用hello_init
  18. module_exit(hello_exit);//对于内置模块,内核在引导时调用该入口点;对于可加载模块,模块插入时调用hello_exit

(2)Makefile文件 

  1. # Makefile2.6
  2. obj-m += module_hello.o #产生module_hello模块的目标文件
  3. CURRENT_PATH := $(shell pwd) #模块所在的当前路径
  4. LINUX_KERNEL := $(shell uname -r) #Linux内核源代码的当前版本
  5. LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径
  6. all:
  7.     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
  8. clean:
  9.     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 完成打印显示后清除环缓冲内的内容),结果如下: 

  1. jelline@jelline:~/project/module_test$ dmesg 
  2. [36478.340651] I bear a charmed life.

(5)查看模块 

    用命令lsmod | grep module_hello或者cat /proc/kallsyms | grep module_hello查看被加入的模块信息,如下: 
  1. module_hello 737 0
(6)卸载模块 
用命令sudo rmmod module_hello卸载模块,dmesg查看内核环缓冲如下:
  1. [36478.340651] I bear a charmed life. 
  2. [36808.842565] Out,out,brief candle!


二、模块间函数调用 

2.1 修改modual_hello

    在上述模块modual_hello加入函数printStr,并且在模块module_call调用该函数。printStr函数源代码如下: 

  1. void printStr(const char *s)
  2. {
  3.     
  4.     printk(s);
  5. }

  6. EXPORT_SYMBOL_GPL(printStr);

模块module_call源代码如下: 

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>

  4. MODULE_LICENSE("GPL");
  5. MODULE_AUTHOR("Jelline");

  6. extern void printStr(char *s); //module_hello.ko

  7. static int hello_init(void)
  8. {
  9.     printStr("Begin module_call.\n");
  10.     return 0;
  11. }

  12. static void hello_exit(void)
  13. {
  14.     printStr("End module_call.\n");
  15. }

  16. module_init(hello_init);
  17. module_exit(hello_exit);

2.2 错误Unknown symbol in module 

 编译好模块module_call,插入时遇到如下错误:

  1. 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 模块描述信息

  1. MODULE_AUTOR("作者信息");
  2. MODULE_DESCRIPTION("模块描述信息");
  3. MODULE_VERSION("版本信息");
  4. MODULE_ALIAS("别名信息");
  5. MODULE_DEVICE_TABLE("设备表信息");

4.2 导出符号表 

  1. EXPORT_SYMBOL() ;//对全部内核代码公开
  2. EXPORT_SYMBOL_GPL();//只适合GPL许可的模块进行调用

4.3 模块参数

    模块参数是模块内部的全局变量,在加载模块时(insmod/modprobe),可以向模块传递的参数值,举例如下:

  1. static char *interface; //模块内部定义变量
  2. module_param(interface, charp, 0644); //module_param(name, type, perm)

  3. sodu insmod module_myirq.ko interface=eth0 //插入模块

除此之外,还有:

  1. module_param_named(name, variable, type, perm); //模块外部参数名称不同于模块内部变量名称
  2. module_param_string(name, string, len, perm);//内核直接拷贝字符串到指定字符数组string
  3. module_param_array(name, type, nump, perm);
  4. module_param_array_named(name, array, type, nump, perm);
  5. MODULE_PARM_DESC()//描述参数

更多信息可查看。 


    关于模块,可以进一步阅读博文《》,尤其是博文《Linux 可加载内核模块剖析》,从系统角度出发,全面、深入浅出剖析了动态内核加载模块。 



参考资料: 

[1]Robert Love著.陈莉君译.Linux内核设计与实现(第二版)[M].北京:机械工业出版社.2006.1 

[2]博文《内核模块编程之入门(一)-话说模块》

阅读(1588) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~