Linux内核的整体结构非常庞大,其包含的组件也非常多,我们需要包含所需的部分功能组件。有两种方法:一种是将所需的功能组件编译进内核。二是,将所需的功能组件编译成独立于内核的模块,需要时动态加载进内核。通常采用第二种方式,它的好处是:
n 模块本身不被编译入内核映像,从而控制了内核的大小。
n 模块一旦被加载,它就和内核中的其他部分完全一样。
一个简单的内核模块的例子:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_INFO " Hello World enter\n"); return 0; }
static void hello_exit(void) { printk(KERN_INFO " Hello World exit\n "); }
module_init(hello_init); module_exit(hello_exit);
MODULE_AUTHOR("Song Baohua"); MODULE_DESCRIPTION("A simple Hello World Module"); MODULE_ALIAS("a simplest module"); |
常用命令:/sbin/insmod, /sbin/modprobe, /sbin/lsmod, /sbin/modinfo, /sbin/rmmod
Linux内核模块的程序结构
一个Linux内核模块主要由以下几部分组成:
模块加载函数(必须)
模块卸载函数(必须)
模块许可证声明(必须)
常见的有:MODULE_LICENSE(“Dual BSD/GPL”)
模块参数(可选)
模块导出符号(可选)
模块作者等信息声明(可选)
模块加载函数
一般以__init标识声明,典型的加载函数的形式如下:
static int __init initialization_function(void) { /*初始化代码*/ } module_init(initialization_function); |
在Linux 2.6内核中,可以使用request_module(const char *fmt, …)函数加载内核,如下所示:
request_module(module_name);
或
request_module(“char-major-%d-%d”, MAJOR(dev), MINOR(dev));
模块卸载函数
static void __exit cleanup_function(void) { /*释放代码*/ } module_exit(cleanup_function); |
模块参数
可以用module_param(参数名,参数类型,参数读/写权限)来定义一个参数。如:
static char *book_name = “bookname”; static int num = 4000; module_param(book_name, charp, S_IRUGO); module_param(num, int, S_IRUGO); |
参数类型可以是:byte, short, ushort, int , uint, long, ulong, charp, bool, invbool(布尔的反)。
具体代码如下:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
static char *book_name = "dissecting Linux Device Driver"; static int num = 4000;
static int book_init(void) { printk(KERN_INFO " book name:%s\n",book_name); printk(KERN_INFO " book num:%d\n",num); return 0; } static void book_exit(void) { printk(KERN_INFO " Book module exit\n "); } module_init(book_init); module_exit(book_exit); module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO);
MODULE_AUTHOR("Song Baohua, author@linuxdriver.cn"); MODULE_DESCRIPTION("A simple Module for testing module params"); MODULE_VERSION("V1.0"); |
相应的Makefile代码:
obj-m := hello.o obj-m += book.o
all: $(MAKE) -C /usr/src/kernels/2.6.23.1-42.fc8-i686/ M=$(shell pwd) modules
clean: rm -f *.o *.ko *.mod.o |
符号导出
命令cat /proc/kallsyms可查看内核符号表。它记录了符号以及符号所在的内存地址。模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);只适用于包含GPL许可权的模块。
例子如下:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
int add_integar(int a,int b) { return a+b; }
int sub_integar(int a,int b) { return a-b; }
EXPORT_SYMBOL(add_integar);
EXPORT_SYMBOL(sub_integar); |
模块的声明与描述
MODULE_AUTHOR(author): 模块的作者
MODULE_DESCRIPTION(description):模块的描述
MODULE_VERSION(version_string):模块的版本
MODULE_DEVICE_TABLE(table_info):对于USB、PCI等设备
MODULE_ALIAS(alternate_name):模块别名
模块的使用计数
Linux2.4中,使用MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏来管理自己被使用的计数。
Linux2.6中,提供了模块计数管理接口:int try_module_get(struct module *module): void module_put(struct module *module);