一. 摘要linux
这篇文章主要介绍了Linux内核模块的相关概念,以及简单的模块开发过程。主要从模块开发中的经常使用指令、内核模块程序的结构、模块使用计数以及模块的编译等角度对内核模块进行介绍。在Linux系统开发过程当中,以模块的形式开发其重要性不言自明,而在嵌入式设备驱动开发中将驱动程序以模块的形式发布,更是极大地提升了设备使用的灵活性——用户只须要拿到相关驱动模块,再插入到用户的内核中,便可灵活地使用你的设备。shell
二. 文章提纲编程
1. 摘要函数
2. 文章提纲工具
3. 概述编码
4. 模块开发经常使用的指令spa
5. 内核模块程序结构指针
6. 模块使用计数日志
7. 模块的编译code
8. 使用模块绕开GPL
9. 总结
三.概述
Linux内核总体结构已经很庞大,包含了不少的组件,而对于咱们工程师而言,有两种方法将须要的功能包含进内核当中。
一:将全部的功能都编译进Linux内核。
二:将须要的功能编译成模块,在须要的时候动态地添加。
上述两种方式优缺点分析:
第一种:
优势:不会有版本不兼容的问题,不须要进行严格的版本检查
缺点:生成的内核会很大;要在现有的内核中添加新的功能,则要编译整个内核
第二种:
优势:模块自己不编译进内核,从而控制了内核的大小;模块一旦被加载,将和其它的部分彻底同样。
缺点:可能会有内核与模块版本不兼容的问题,致使内核崩溃;会形成内存的利用率比较低。
四.模块开发经常使用的指令
在内核模块开发的过程当中经常使用的有如下指令。
1) insmod: 将模块插入内核中,使用方法:#insmod XXX.ko
2) rmmod: 将模块从内核中删除,使用方法:#rmmod XXX.ko
3) lsmod: 列表显示全部的内核模块,能够和grep指令结合使用。使用方法:#lsmod | grep XXX
4) modprobe: modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程当中发生错误,在modprobe会卸载整组的模块。依赖关系是经过读取 /lib/modules/2.6.xx/modules.dep获得的。而该文件是经过depmod 所创建。
5) modinfo: 查看模块信息。使用方法:#modinfo XXX.ko
6) tree –a: 查看当前目录的整个树结构。使用方法:#tree -a
五.内核模块程序结构
1) 模块加载函数(通常须要)
在用insmod或modprobe命令加载模块时,该函数被执行。完成模块的初始化工做。
Linux内核的模块加载函数通常用__init标识声明,模块加载函数必须以module_init(函数名)的形式被指定。该函数返回整型值,若是执行成功,则返回0,初始化失败时则返回错误编码,Linux内核当中的错误编码是负值,在<linux/errno.h>中定义。
在Linux中,标识__init的函数在链接时放在.init.text这个区段,并且在.initcall.init中保留一份函数指针,初始化的时候内核会根据这些指针调用初始化函数,初始化结束后释放这些init区段(包括前二者)。
代码清单:
1 static int __init XXX_init(void) 2 3 { 4 5 return 0; 6 } 7 8 9 10 moudle_init(XXX_init);
2) 模块卸载函数(通常须要)
在用rmmod或modprobe命令卸载模块时,该函数被执行。完成与加载相反的工做。
模块的卸载函数和模块加载函数实现相反的功能,主要包括
代码清单:
1 static void __exit XXX_exit(void) 2 3 { 4 5 } 6 7 8 9 moudle_exit(XXX_exit);
3) 模块许可证声明(必须)
若是不声明,则在模块加载时会收到内核被污染的警告,通常应遵循GPL协议。
代码清单:
1 MODULE_LICENSE("GPL");
4) 模块参数(可选)
模块在被加载时传递给模块的值,自己应该是模块内部的全局变量。
示例程序book.c
1 #include <linux/init.h> 2 3 #include <linux/module.h> 4 5 6 7 static char *bookName = "Good Book."; 8 9 static int bookNumber = 100; 10 11 12 13 static int __init book_init(void) 14 15 { 16 17 printk(KERN_INFO "Book name is %s\n", bookName); 18 19 printk(KERN_INFO "Book number is %d\n", bookNumber); 20 21 return 0; 22 23 } 24 25 26 27 static void __exit book_exit(void) 28 29 { 30 31 printk(KERN_INFO "Book module exit.\n"); 32 33 } 34 35 36 37 module_init(book_init); 38 39 module_exit(book_exit); 40 41 module_param(bookName, charp, S_IRUGO); 42 43 module_param(bookNumber, int, S_IRUGO); 44 45 46 47 MODULE_LICENSE("GPL");
在向内核插入模块的时候能够用如下方式,而且能够在内核日志中看到模块加载之后变量已经有了值。
5) 模块导出符号(可选)
使用模块导出符号,方便其它模块依赖于该模块,并使用模块中的变量和函数等。
在Linux2.6的内核中,/proc/kallsyms文件对应着符号表,它记录了符号和符号对应的内存地址。对于模块而言,使用下面的宏能够导出符号。
1 EXPORT_SYMBOL(符号名);
或
1 EXPORT_GPL_SYMBOL(符号名);
6) 模块信息(可选)
模块信息则是指模块的做者信息等。
六.模块使用计数
Linux内核提供了MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT宏来管理模块使用计数。可是对于内核模块而言,通常不会本身管理使用计数。
七.模块的编译
将下面的Makefile文件放在book.c同级的目录下,而后使用#make命令或者#make all命令编译便可生成book.ko模块文件。
对应的Makefile:
1 ifneq ($(KERNELRELEASE),) 2 3 mymodule_objs := book.o 4 5 obj-m := book.o 6 7 else 8 9 PWD := $(shell pwd) 10 11 KVER ?= $(shell uname -r) 12 13 KDIR := /usr/src/linux-headers-2.6.38-8-generic 14 15 16 17 all: 18 19 $(MAKE) -C $(KDIR) M=$(PWD) 20 21 clean: 22 23 rm -rf *.mod.c *.mod.o *.ko *.o *.tmp_versions *.order *symvers 24 25 endif
八.使用模块绕开
若是功能不编译成模块,则没法绕开GPL,编译成模块后公司发布产品则只须要发布模块,而不须要发布源码。为了Linux系统可以支持模块,须要作如下的工做:
九.总结
本文主要介绍内核模块的概念和基本编程方法,内核模块主要由加载、卸载函数功能函数等一系列声明组成。它能够被传入参数,也能够导出符号,供其它的模块使用。