1 模块相关命令×mod×:
“insmod ./hello.ko”命令可以加载它
“rmmod hello”命令可以卸载它
lsmod 命令可以获得系统中加载了的所有模块以及模块间的依赖关系,lsmod 命令实际上读取并分析/proc/modules文件,
2 Linux 内核模块的程序结构
代码清单4.1 一个最简单的 Linux 内核模块1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4 static int hello_init(void)
5 {
6 printk(KERN_ALERT " Hello World enter\n");
7 return 0;
8 }
9 static void hello_exit(void)
10 {
11 printk(KERN_ALERT " Hello World exit\n ");
12 }
13 module_init(hello_init);
14 module_exit(hello_exit);
15
16 MODULE_AUTHOR("Song Baohua");
17 MODULE_DESCRIPTION("A simple Hello World Module");
18 MODULE_ALIAS("a simplest module");
一个Linux 内核模块主要由以下几个部分组成。
l 模块加载函数(必须)。
当通过 insmod 或modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
l 模块卸载函数(必须)。
当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
l 模块许可证声明(必须)。
模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。 在Linux 2.6 内核中,可接受的LICENSE 包括“GPL”、“GPL v2”、“GPL andadditional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。 大多数情况下,内核模块应遵循GPL 兼容许可权。Linux 2.6内核模块最常见的是以 MODULE_LICENSE( "Dual BSD/GPL" )语句声明模块采用 BSD/GPL 双LICENSE。
l 模块参数(可选)。
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
l 模块导出符号(可选)。
内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。
l 模块作者等信息声明(可选)
1.1 模块加载函数
Linux 内核模块加载函数一般以_ _init 标识声明,典型的模块加载函数的形式如代码清单 4.2所示。代码清单 4.2 内核模块加载函数
1 static int _ _init initialization_function(void)
2 {
3 /* 初始化代码 */
4 }
5 module_init(initialization_function);
模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回0。而在初始化失败时,应该返回错误编码。在Linux 内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用 perror 等方法把它们转换成有意义的错误信息字符串。
在 Linux 2.6 内核中,可以使用request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用
request_module(module_name);
或
request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
来加载其他内核模块。
在 Linux 内核中,所有标识为_ _init的函数在连接的时候都放在.init.text这个区段内,此外,所有的_ _init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_ _init函数,并在初始化完成后释放 init区段(包括.init.text,.initcall.init等)。
1.2 模块卸载函数
Linux内核模块卸载函数一般以_ _exit标识声明,典型的模块卸载函数的形式如代码清单 4.3所示。代码清单 4.3 内核模块卸载函数
1 static void _ _exit cleanup_function(void)
2 {
3 /* 释放代码 */
4 }
5 module_exit(cleanup_function);
模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。
通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示。
l 若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。
l 若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。
l 若模块加载函数申请了硬件资源(中断、DMA通道、I/O 端口和I/O 内存等)的占用,则模块卸载函数应释放这些硬件资源。
l 若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。和_ _init一样,_ _exit 也可以使对应函数在运行完成后自动回收内存。实际上,_
_init 和_ _exit 都是宏,其定义分别为:
#define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))
和
#ifdef MODULE
#define _ _exit _ _attribute_ _ ((_ _section_ _(".exit.text")))
#else
#define _ _exit _ _attribute_used_ _ _ _attribute_ _ ((_ _section__(".exit.text")))
#endif
数据也可以被定义为_initdata和_exitdata,这两个宏分别为:
#define _ _initdata_ _attribute_ _ ((_ _section_ _ (".init.data")))
和
#define _ _exitdata_ _attribute_ _ ((_ _section_ _(".exit.data")))
1.3 模块参数
我们可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了一个整型参数和一个字符指针参数:static char *book_name = "深入浅出Linux 设备驱动";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或modprobe)模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的默认值。
参数类型可以是 byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool或 invbool(布尔的反),在模块被编译时会将module_param 中声明的类型与变量定义的类型进行比较,判断是否一致 。
模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当“参数读
/写权限”为 0时,表示此参数不存在 sysfs 文件系统下对应的文件节点,如果此模块
存在“参数读/写权限”不为 0 的命令行参数,在此模块的目录下还将出现 parameters
目录,包含一系列以参数名命名的文件节点,这些文件的权限值就是传入
module_param()的“参数读/写权限”,而文件的内容为参数的值。
除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组
类型,数组长,参数读/写权限)”。从2.6.0~2.6.10版本,需将数组长变量名赋给“数组
长”,从 2.6.10 版本开始,需将数组长变量的指针赋给“数组长”,当不需要保存实际
输入的数组元素个数时,可以设置“数组长”为 NULL。
运行 insmod 或 modprobe 命令时,应使用逗号分隔输入的数组元素。
现在我们定义一个包含两个参数的模块(如代码清单 4.4 所示),并观察模块加载
时被传递参数和不传递参数时的输出。
代码清单 4.4 带参数的内核模块
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
45
static char *book_name = "dissecting Linux Device Driver";
6 static int num = 4000;
78
static int book_init(void)
9 {
10 printk(KERN_INFO " book name:%s\n",book_name);
11 printk(KERN_INFO " book num:%d\n",num);
12 return 0;
13 }
14 static void book_exit(void)
15 {
16 printk(KERN_ALERT " Book module exit\n ");
17 }
18 module_init(book_init);
19 module_exit(book_exit);
20 module_param(num, int, S_IRUGO);
21 module_param(book_name, charp, S_IRUGO);
22
23 MODULE_AUTHOR("Song Baohua, author@linuxdriver.cn");
24 MODULE_DESCRIPTION("A simple Module for testing module params");
25 MODULE_VERSION("V1.0");
对上述模块运行“insmod book.ko”命令加载,相应输出都为模块内的默认值,通
过查看“/var/log/messages”日志文件可以看到内核的输出,如下所示:
[root@localhost driver_study]# tail -n 2 /var/log/messages
Jul 2 01:03:10 localhost kernel: <6> book name:dissecting Linux Device
Driver
Jul 2 01:03:10 localhost kernel: book num:4000
当用户运行“insmod book.ko book_name='GoodBook' num=5000”命令时,输出的