设备驱动(4)-初始化、清理以及错误处理

初始化、清理以及错误处理

模块初始化

static int __init initialization_function(void)
{
	//初始化代码
}
module_init(initialization_function);

       初始化函数应该被声明为static,因为这种函数在特定文件之外是没有其他意义的,并且一个模块函数如果要对内核其他部分可见,那么就必须显式导出,所以这个static并不是强制要求的。__init标记用于暗示内核该函数只在初始化期间使用。在模块被装载之后,模块装载器就会将初始化函数扔掉。这样可以将该函数占用的内存释放出来。__init以及__initdata的使用是可选的,但是推荐使用。记得当初始化完成之后就不要再使用带有__init或者__initdata的函数或者是变量了。

       module_init的使用是强制性的,这个宏会在模块的目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。没有这个定义,初始化函数永远不会被调用。

模块卸载

static void __exit cleanup_function(void)
{
	//清除代码
}

       清除函数没有返回值,因此被声明为void,__exit修饰词标记该代码仅用于模块卸载(编译器将把该函数放在特殊的ELF段中)。如果模块被直接内嵌到内核中,或者内核的配置不允许卸载模块,则标记为__exit的函数将被简单的丢弃。处于以上原因,被标记为__exit的函数只能在模块被卸载或者系统关机时被调用,其他的任何用法都是错误的。和初始化函数一样,module_exit声明对于帮助内核找到模块的清除函数是必需的。如果一个模块没有定义清理函数,则内核不允许卸载该模块。

初始化过程中的错误处理
       当我们在内核中注册设备的时候,要时刻铭记注册可能会失败。即使最简单的动作,都需要内存的分配,而所需要的内存可能无法获得。因此模块代码必须始终检查返回值,并确保所请求的操作已真正成功。
       如果在注册设备时遇到任何错误,首先要判断模块是否可以继续初始化。通常,在某个注册失败后可以通过降低功能来继续运转。因此,只要可能,模块应该继续向前并尽可能提供其功能。
       如果在发生了某个特定类型的错误之后无法继续装载模块,则要将出错之前的任何注册工作撤销掉。因为linux中没有记录每个模块都注册了那些模块的功能。因此,当模块的初始化出现错误之后,模块必须自行撤销已注册的设备。如果由于某种原因我们未能撤销已注册的设备,则内核会处于一种不稳定状态,这是因为内核中包含了一些指向并不存在的代码的内部指针。在这种情况下,唯一有效的办法就是重启系统。

  • goto 处理方式
           错误恢复的处理有时使用goto语句比较有效。通常情况下很少使用goto,但在处理错误的时候(可能是唯一的情况)它却非常有用。这种情况下的goto的仔细使用可避免大量复杂的、高度缩进的代码。因此,内核经常使用goto来处理错误。
int __init my_init_function(void)
{
	int errr;
	/*使用指针和名称注册*/
	err = register_this(ptr1, "skull");
	if(err) goto fail_this;
	err = register_that(ptr1, "skull");
	if(err) goto fail_that;
	err = register_those(ptr1, "skull");
	if(err) goto fail_those;
	return 0;
	
	fail_those:unregister_that(ptr2, "skull");
	fail_that:unregister_this(ptr1, "skull");
	tail_this:return err;
}

模块的清除函数需要撤销初始化函数所注册的所有设施,并且习惯上以相反与注册的顺序撤销。

void __exit my_cleanup_function(void)
{
	unregister_those(ptr3, "skull");
	unregister_that(ptr2, "skull");
	unregister_this(ptr1, "skull");
	return;
}
  • 在初始化函数中调用清除函数
struct something *item1 = NULL;
struct somethingelse *items2 = NULL;
int stuff_ok = 1;

void my_cleanup(void)
{
	if(item1)
		release_thing(item1);
	if(item2)
		release_thing2(item2);
	if(stuff_ok)
		unregister_stuff();
}
int __init my_init(void)
{
	int err = -ENOMEM;
	item1 = allocate_thing(arguments);
	item2 = allocate_thing2(arguments);
	if(!item || !item2)
	{
		goto fail;
	}
	err = register_stuff(item1, item2);
	if(!err)
		stuff_ok = 1;
	else
		goto fail;
	return 0;
	fail:
		mycleanup();
		return err;
}

       推荐使用
模块的竞态

  1. 在注册完成之后,内核的某些部分可能会立即使用我们刚才注册的任何设备,换句话说,在初始化函数还在运行的时候,内核就完成有可能调用我们的模块。因此,在首次注册完成之后,代码就应该准备好被内核的其他部分调用;在用来支持某个设备的所有内部初始化完成之前,不要注册任何设备,即调用cdev_add()函数。
  2. 当初始化失败但是内核的某些部分已经在使用模块所注册的某个设备时应该怎么处理?如果这种情况可能发生在我们的模块上,那么就不应该有初始化失败的情况发生,毕竟模块已经成功导出了可用的功能以及符号。如果初始化一定要失败,则应该仔细处理内核其他部分正在进行的操作,并且等待这些操作的完成。

模块的参数
       由于系统的不同,驱动程序需要的参数也许会发生变化,包括设备号以及其他一些用来控制驱动程序操作方式的参数。为了满足这种需求,内核允许对驱动程序指定参数,而这些参数可以装载驱动程序时改变。
       这些参数的值可在运行insmod或modprobe命令装载模块时赋值,而modprob还可以从它的配置文件(/etc/modprob.conf)中读取参数值。这两个命令可以接受几种参数类型的赋值。
       在insmod改变模块参数之前,模块必须让这些参数对insmod命令可见。参数必须使用module_param宏来声明,这个宏在moduleparam.h中定义,module_param需要三个参数:变量的名称、类型以及用于sysfs入口项的访问需要掩码。这个宏必须放在任何函数之外,通常是源文件的头部,例如

static char *whom = "world";
static int howmany = 1;
module_patam(howmany, int, S_IRUGO);
module_patam(whom, charp, S_IRUGO);

       模块装载器也支持数组参数,在提供数组值时使用逗号划分各数组成员。要声明数组参数,需要使用下面的宏:module_param_array(name,type,num,perm);
       如果我们需要的类型不在上面所列出的清单中,模块代码中的钩子可让我们来定义这些类型。具体细节阅读moduleparam.h文件。所有的模块参数都应该给定一个默认值。insmod只会在用户明确设置了参数的值的情况下才会修改参数的值。模块可以根据默认值来判断是否是一个显式指定的参数。
       module_patam中的最后一个成员是访问许可值,用来控制谁能够访问sysfs中对模块参数的表述,我们应该使用<linux/stat.h>中存在的定义。S_IRUGO表示任何用户都对/sys/module中出现的该参数具有读权限,但是不能够修改。S_IWUSR允许root用户修改/sys/module中出现的该参数。内核不会通知模块该参数发生了变化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值