一个 Linux 内核模块程序结构主要由以下几个部分组成:
- 模块加载函数(必须) 。
当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。 - 模块卸载函数(必须) 。
当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。 - 模块许可证声明(必须) 。
模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。大多数情况下,内核模块应遵循 GPL 兼容许可权。Linux 2.6 内核模块最常见的
是以 MODULE_LICENSE( “Dual BSD/GPL” ) 语句声明模块采用 BSD/GPL 双LICENSE。 - 模块参数(可选) 。
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。 - 模块导出符号(可选) 。
内核模块可以导出符号(symbol,对应于函数或变量) ,这样其他模块可以使用本模块中的变量或函数。 - 模块作者等信息声明(可选) 。
模块加载函数
Linux 内核模块加载函数一般以_ _init 标识声明:
1 static int _ _init initialization_function(void)
2 {
3 /* 初始化代码 */
4 }
5 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));
模块卸载函数
Linux 内核模块卸载函数一般以_ _exit 标识声明:
1 static void _ _exit cleanup_function(void)
2 {
3 /* 释放代码 */
4 }
5 module_exit(cleanup_function);
通常来说,模块卸载函数要完成与模块加载函数相反的功能 :
- 若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。
- 若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。
- 若模块加载函数申请了硬件资源(中断、DMA 通道、I/O 端口和 I/O 内存等)
的占用,则模块卸载函数应释放这些硬件资源。 - 若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。
模块参数
`module_param(参数名,参数类型,参数读/写权限)`,
参数即模块中用到的全局变量,加载模块时可以通过
`insmod(或 modprobe)模块名 参数名=参数值”` ,
向模块传递参数。如果不传递,参数将使用模块内定义的默认值。
导出符号
Linux 2.6 的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。
模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
导 出 的 符 号(或者说函数) 将 可 以 被 其 他 模 块 使 用 , 使 用 前 声 明 一 下 即 可 。EXPORT_SYMBOL_GPL()只适用于包含 GPL 许可权的模块。
模块声明与描述
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
模块使用计数
int try_module_get(struct module *module);
该函数用于增加模块使用计数;若返回为 0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。
void module_put(struct module *module);
该函数用于减少模块使用计数。
Linux 2.6 内核为不同类型的设备定义了 struct module *owner 域,用来指向管理此设备的模块。当开始使用某个设备时,内核使用 try_module_get(dev->owner)去增加管理此设备的 owner 模块的使用计数;当不再使用此设备时,内核使用module_put(dev->owner)减少对管理此设备的 owner 模块的使用计数。这样,当设备在使用时,管理此设备的模块将不能被卸载。只有当设备不再被使用时,模块才允许被
卸载。