第二章:构造和允许模块(part2)

内核符号表

当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。

模块层叠技术是一个常用的方案。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, …);

 



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值