内核符号表
当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。
模块层叠技术是一个常用的方案。modprobe是处理层叠模块的一个使用工具,它除了装入指定模块外还同时装入了指定模块所依赖的其他模块。(从当前目录装入自己的模块仍需要insmod,因为modprobe只能从标准的以安装模块目录中搜索需要装入的模块)
如果一个模块需要向其他模块导出符号,则应该使用下面的宏:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
注意:_GPL版本使得导出的符号只能被GPL许可证下的模块使用;符号必须在模块文件的全局部分导出,不能再函数中导出(因为两个宏会被扩展为一个特殊变量,而这个变量必须是全局变量,该变量在可执行文件的一个特殊的部分保存,即”ELF段”,装载模块时,内核通过这个段来寻找导出的变量)。
预备知识
所有的模块代码都包含下面两行代码:
#include <linux/module.h>
#include <linux/init.h>
module.h包含可装载模块需要的大量符号和函数的定义。init.h是用来指定初始化和清除函数。
此外,大多数模块代码也包含moduleparam.h,这样我们可以在装载模块时向模块传递参数。
模块中包含的其他描述性定义:
MODULE_LICENSE (“GPL”);
模块一般需指定所使用的许可证。内核可识别的许可证有”GPL”、”GPL v2”、”GPL and additional rights”、”Dual BSD/GPL”、”Dual MPL/GPL”以及”Proprietary”。
MODULE_AUTHOR(“Lujianpeng”); 描述模块作者。
MODULE_DESCRIPTION (“Drive for Device”); 描述模块用途。
MODULE_VERSION (“2.6.10”); 代码修订号。
MODULE_ALIAS(); 模块别名。
MODULE_DEVICE_TABLE(); 用来告诉用户空间模块所支持的设备。
一般情况下,这些声明放在文件的最后。
注意:这些宏描述的定义可以通过modinfo命令来查看。
初始化和关闭
模块的初始化负责注册模块所提供的任何设施(可能是一个完整的驱动程序,也可能仅仅是一个新的软件抽象)。
初始化函数的实际定义通常如下:
static int __init initialization_function(void)
{
/*这里是初始化代码*/
}
module_init(initialization_function);
若干解释:
l 为什么声明为static?因为这种函数在特定文件之外没有其他意义。这并不是强制性规则,因为如果一个函数要对内核其他部分课件,则必须显式导出。
l __init、__initdata的作用:表明该函数尽在初始化期间使用,模块装载之后初始化函数会被扔掉,释放内存以作他用。两者的使用是可选的。在内核源代码会遇到__devinit和__devinitdata,只有在内核未被配置为支持热插拔设备的情况下,这两个标记才会被翻译为_init和__initdata。
l module_init的使用是强制性的。用于说明内核初始化函数所在的位置。
模块可以出则许多不同类型的设施,不同的设施对应具体的内核函数用来完成注册。传递到注册函数的参数通常是一个指向某数据结构的指针,此数据结构中包含指向模块函数的指针。
清除函数
清除函数定义如下:
static void __exit cleanup_function(void)
{
/*这里是清除代码*/
}
Module_exit(cleanup_function);
若干解释:
l 函数无返回值,被声明为void。
l __exit:标记该代码仅用于模块卸载,只能在卸载模块或者关闭系统时被调用,其他的任何调用都是错误的。若模块直接内嵌到内核中,或者内核不允许卸载模块,则被标记__exit的函数将被简单的丢弃。
l module_exit:帮助内核找到模块的清除函数。如果一个模块未定义清除函数,则内核不允许卸载该模块。
初始化过程中的错误处理
如果发生了某个特定类型的错误之后无法继续装在模块,则要将出错之前的任何注册工作撤销掉。错误处理有时使用goto语句比较有效。如:
int __init my_init_function(void)
{
int err;
/*使用指针和名称注册*/
err = register_this(ptr1, “skull”);
if (err) goto fail_this;
err = register_that(ptr2, “skull”);
if (err) goto fail_that;
err = register_those(ptr3, “skull”);
if (err) goto fail_those;
return 0; /*成功*/
fail_those: unregister_that(ptr2, “skull”);
fail_that: unregister_this(ptr1, “skull”);
fail_this: return err; /*返回错误*/
}
在linux内核中,错误编码是定义在<linux/errno.h>中的负整数。
模块的清除函数需要撤销初始化函数所注册的所有设施,习惯上以相反于注册的顺序撤销设施。
当初始化和清除工作涉及很多设施时,goto方法就显得麻烦;此时,我们可以在初始化函数中调用清除函数,当然,清除函数在撤销每项设施的注册之前检查它的状态。(此处可以参看原书代码)
模块装载竞争
在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。
模块参数
使用insmod以及modprobe装载模块时,可以设定其中若干的参数值。这些参数值在程序中必须使用module_param宏来声明。这个宏定义在moduleparam.h中。
module_param(name, int, S_IRUGO); // 用来声明一个变量
module_param_array(name, type, num, perm); // 用来声明一个数组
解释:
name:变量名;type:类型,支持bool, invbool, charp, int, long, short, uint, ulong, ushort; perm:访问许可值; num:一个整数变量。
所有模块参数需要给一个默认值,insmod只会在用户明确的设置了参数的值的情况下才会改变参数的值。模块可以根据默认值来判断是否是一个显式指定的值。
查漏补缺
#include <linux/sched.h>
该文件包含驱动程序使用的大部分内核API定义。
vermagic.o
内核源代码目录一个目标文件,它描述了模块的构造环境。
#include <linux/kernel.h>
int printk(const char *fmt, …);