linux内核入门之module

来源:互联网 发布:淘宝删除子账号后 编辑:程序博客网 时间:2024/06/17 17:55
一、模块的概念

模块是一种向linux内核添加设备驱动程序、文件系统及其他组件的有效办法。无需重新编译新内核和重启系统就可以向内核加入功能。增加了linux的可扩展性。

优点:
        将功能模块后,内核image就不会发生膨胀。
        方便测试新的内核特性。

二、HelloWorld模块

文件hello.c:

#include <linux/module.h> /*定义了与module相关的一些宏和函数,如MODULE_LICENSE等*/#include <linux/init.h>   /*定义了module_init和module_exit宏*/#include <linux/kernel.h> /*定义了printk函数*//* * 初始化函数,当模块装载时被调用,如果成功装载则返回0, * 否则返回非0 */static int hello_init(void){        printk(KERN_ALERT "Hello world\n");        return 0;}/* * 退出函数,当模块被卸载时被调用,释放在初始化函数中申请的资源 */static void hello_exit(void){        printk(KERN_ALERT "Goodbye world\n");}module_init(hello_init);/*将hello_init函数注册为模块init函数*/module_exit(hello_exit);/*将hello_eixt函数注册为模块exit函数*/MODULE_LICENSE("GPL");/*模块所使用的许可协议*/MODULE_AUTHOR("ice");/*模块的作者*/

三、编译模块

生成最终模块文件需要编写Makefile,如下:

文件Makefile:

ifneq ($(KERNELRELEASE),)        obj-m := hello.oelse        KERNELDIR ?= /lib/modules/`uname -r`/build        PWD := `pwd`default:        $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendif



这个makefile被调用两次,在命令行执行make时,KERNELRELEASE未被设置,所以会执行else中语句。
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules会进入到内核代码树的目录并读取内核顶层的Makefile。
该Makefile会设置KERNELRELEASE,再次读取模块目录下的Makefile时,设置obj-m从而开始进行模块的编译工作。

-C : 在执行make之前先进入$(KERNELDIR)目录,即内核树的顶层目录,然后再执行make,此时make读取的makefile就是内核树顶层目录中的Makefile文件。
M=$(PWD) : 将M的参数值传递给内核树的顶层Makefile,即M的值为当前的模块源文件所在的目录。
modules : make的执行目标

obj-m := hello.o  表示最终生成模块hello.ko文件

如果源文件包含多个.c文件(file1.c, file2.c),obj-m应编写如下:
obj-m := hello.o
hello-objs := file1.o file2.o

四、装载和卸载模块

使用insmod和modprobe命令来装载模块。
命令如:insmod ./hello.ko

insmod装载模块流程:
1.insmod会使用系统调用函数sys_init_module
2.sys_init_module给模块分配分存
3.sys_init_module将模块复制到内存区域
4.调用模块的初始化函数

modprobe与insmod的区别在于modprobe会处理模块的依赖关系,将依赖的模块装载到内核中。

使用lsmod命令可以查看当前系统已经安装的模块信息列表,该命令读取/proc/modules文件。

使用rmmod和modprobe -r命令来卸载已安装模块。
命令如:rmmod hello

五、模块参数

通过模块参数,可以在系统启动或模块装载时指定或修改参数的值,类似于命令参数,不必将参数的值写死在代码中,增强了模块的灵活性,同时用户程序可以通过模块参数读取模块的数据。

外部参数:装载模块时,在命令中使用名称,如insmod ./hello.ko who=jack
内部变量: 模块代码里面使用的变量名称

module_param(name, type, perm);
name: 外部参数和内部变量同为name。
type: 参数或变量的数据类型,值为:
        byte
        short
        ushort
        int
        uint
        long
        ulong
        charp: 字符指针,在传递数据时,由内核为其分配内存,并将字符指针指向内存的首地址
        bool
        invbool
perm: 模块参数在sysfs文件系统下对应的文件访问权限,一般在/sys/module/生成,如果为0,不会生成对应文件。
        其值可以为S_IRUGO | S_IWUSR或0644等。

module_param_named(name, variable, type, perm);
将外部参数name与内部变量variable关联起来,这样外部参数名称就不必要与内部变量同名,可以隐藏内部变量的名称。

module_param_string(name, string, len, perm);
字符串形式的模块参数,等价于module_param(name, charp, perm)。

module_param_array(name, type, nump, perm);
数组形式的模块参数,命令行中参数值以逗号分隔。
type: 数组元素的数据类型。
nump: 指针类型,用于存放从外部参数传递过来的数组元素个数,当传递元素的个数超过了数组的大小时会报错。

module_param_array_named(name, array, type, nump, perm);

以上module_开头都是宏,这些模块参数宏定义在<linux/moduleparam.h>文件中。

MODULE_PARM_DESC(name, description)
描述参数,可以通过modinfo命令查看参数描述信息

六、模块符号表
模块可以导出自己的函数或变量供其它模块使用。这样提高了代码的复用性。
模块导出的函数信息存放在模块导出符号表中,该符号表可看作为内核的API,导出的函数必须非static。

EXPORT_SYMBOL(name)
EXPORT_SYMBOL_GPL(name)
EXPORT_SYMBOL_GPL导出的name只能被使用GPL许可协议的模块使用。

七、内核模块与应用程序
1.应用程序为完成某个任务,而模块一般服务将来某个请求。
2.模块退出时必须清理初始化时申请的资源,而应用程序不必要。
3.模块是以类似事件驱动的方式工作,而不是所有的应用程序都是事件驱动。
4.应用程序通过链接库使用外部函数,而模块只能通过导出符号表。
5.应用程序的错误是无害的,因为模块是运行在内核空间,所以模块的错误可能会影响整个系统。

八、综合示例
1.目录结构
|--Makefile
|--hello/
     |--hello.c
     |--hello.h
     |--Makefile
|--other/
     |--other.c
     |--Makefile
 
2.源代码  
2.1.顶层Makefile:
obj-y = hello/ other/KERNELDIR ?= /lib/modules/`uname -r`/buildPWD := `pwd`modules:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

 

2.2.文件hello/hello.h

void hello_prt(void);

    
2.3.文件hello/hello.c:
#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include "hello.h"/*charp*/static char *chp;module_param(chp, charp, 0600);MODULE_PARM_DESC(chp, "[char pointer]");/*string*/static char str[100];module_param_string(str, str, 100, S_IRUSR | S_IWUSR);MODULE_PARM_DESC(str, "[string]");/*array*/static int len;static int intarr[5];module_param_array(intarr, int, &len, 0600);MODULE_PARM_DESC(intarr, "[array of int]");void hello_prt(void){        printk(KERN_ALERT "hello\n");}EXPORT_SYMBOL(hello_prt);static int __init hello_init(void){        int i;        printk(KERN_ALERT "Hello world\n");        printk(KERN_ALERT "chp:%s\n", chp);        printk(KERN_ALERT "str:%s\n", str);        for (i = 0; i < len; i++) {                printk(KERN_ALERT "intarr[%d]=%d\n", i, intarr[i]);        }        return 0;}static void hello_exit(void){        printk(KERN_ALERT "Goodbye world\n");}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ice");



2.4.文件hello/Makefile:
ifneq ($(KERNELRELEASE),)    obj-m := hello.oelse    KERNELDIR=/lib/modules/`uname -r`/build        PWD := `pwd`default:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:    rm -rf *.o *.mod.c *.ko    rm -rf Module.* .*cmd .tmp_versions


2.5.文件other/other.c:
#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include "../hello/hello.h"static int other_init(void){        printk(KERN_ALERT "init other\n");        hello_prt();/*使用hello模块的导出函数*/        return 0;}static void other_exit(void){        printk(KERN_ALERT "exit other\n");}module_init(other_init);module_exit(other_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ice");

2.6.文件other/Makefile:
ifneq ($(KERNELRELEASE),)    obj-m := other.oelse    KERNELDIR=/lib/modules/`uname -r`/build        PWD := `pwd`default:    $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:    rm -rf *.o *.mod.c *.ko    rm -rf Module.* .*cmd .tmp_versions

3.测试

进入到顶层Makefile所在的目录,执行make
[root@ice-centos module]# makemake -C /lib/modules/2.6.18-371.1.2.el5.centos.plus/build M=/root/test/module modulesmake[1]: Entering directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'  CC [M]  /root/test/module/hello/hello.o  CC [M]  /root/test/module/other/other.o  Building modules, stage 2.  MODPOST  CC      /root/test/module/hello/hello.mod.o  LD [M]  /root/test/module/hello/hello.ko  CC      /root/test/module/other/other.mod.o  LD [M]  /root/test/module/other/other.komake[1]: Leaving directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'[root@ice-centos module]# lshello/  Makefile  Module.markers  Module.symvers  other/[root@ice-centos module]# modinfo hello/hello.ko other/other.ko ==> 查看模块文件信息filename:       hello/hello.koauthor:         icelicense:        GPLsrcversion:     5EBD27C704FD9027F9DB618depends:        vermagic:       2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1parm:           chp:[char pointer] (charp)parm:           str:[string] (string)parm:           intarr:[array of int] (array of int)filename:       other/other.koauthor:         icelicense:        GPLsrcversion:     6417B31CEB48894C5F7EE55depends:        hello ==> 因为使用了hello模块的函数,所以依赖hello模块vermagic:       2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1[root@ice-centos module]# insmod hello/hello.ko chp=aa str=abcdefg intarr=11,22,33,44[root@ice-centos module]# insmod other/other.ko  [root@ice-centos module]# tail -9 /var/log/messagesOct 30 16:02:01 ice-centos kernel: Hello worldOct 30 16:02:01 ice-centos kernel: chp:aaOct 30 16:02:01 ice-centos kernel: str:abcdefgOct 30 16:02:01 ice-centos kernel: intarr[0]=11Oct 30 16:02:01 ice-centos kernel: intarr[1]=22Oct 30 16:02:01 ice-centos kernel: intarr[2]=33Oct 30 16:02:01 ice-centos kernel: intarr[3]=44Oct 30 16:02:08 ice-centos kernel: init otherOct 30 16:02:08 ice-centos kernel: hello ==> 调用hello_prt函数打印的信息[root@ice-centos module]# ls /sys/module/hello/ ==> hello模块装载成功后,会在/sys/module目录下生成hello模块目录parameters/ refcnt      sections/   srcversion  [root@ice-centos module]# ll /sys/module/hello/parameters/ ==> hello模块的模块参数total 0-rw------- 1 root root 4096 Oct 30 16:05 chp-rw------- 1 root root 4096 Oct 30 16:05 intarr-rw------- 1 root root 4096 Oct 30 16:05 str[root@ice-centos module]# rmmod hello ==> 由于hello模块被other模块引用,所以等other模块卸载后才能卸载hello模块ERROR: Module hello is in use by other[root@ice-centos module]# rmmod other[root@ice-centos module]# rmmod hello[root@ice-centos module]# tail -2 /var/log/messagesOct 30 16:03:29 ice-centos kernel: exit otherOct 30 16:03:30 ice-centos kernel: Goodbye world

原创粉丝点击