你必须记住一件事, 在注册内核设施时, 注册可能失败. 即便最简单的动作常常需要内存 分配, 分配的内存可能不可用. 因此模块代码必须一直检查返回值, 并且确认要求的操作 实际上已经成功.
如果在你注册工具时发生任何错误, 首先第一的事情是决定模块是否能够无论如何继续初 始化它自己. 常常, 在一个注册失败后模块可以继续操作, 如果需要可以功能降级. 在任 何可能的时候, 你的模块应当尽力向前, 并提供事情失败后具备的能力.
如果证实你的模块在一个特别类型的失败后完全不能加载, 你必须取消任何在失败前注册 的动作. 内核不保留已经注册的设施的每模块注册, 因此如果初始化在某个点失败, 模块 必须能自己退回所有东西. 如果你无法注销你获取的东西, 内核就被置于一个不稳定状态; 它包含了不存在的代码的内部指针. 这种情况下, 经常地, 唯一的方法就是重启系统. 在 初始化错误发生时, 你确实要小心地将事情做正确.
错误恢复有时用 goto 语句处理是最好的. 我们通常不愿使用 goto, 但是在我们的观念里, 这是一个它有用的地方. 在错误情形下小心使用 goto 可以去掉大量的复杂, 过度对齐的, "结构形" 的逻辑. 因此, 在内核里, goto 是处理错误经常用到, 如这里显示的.
下面例子代码( 使用设施注册和注销函数)在初始化在任何点失败时做得正确:
int init my_init_function(void)
{
int err;
err = register_this(ptr1, "skull"); /* registration takes a pointer and a name */ 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; /* success */ fail_those:
unregister_that(ptr2, "skull"); fail_that:
unregister_this(ptr1, "skull"); fail_this:
return err; /* propagate the error */
}
这段代码试图注册 3 个(虚构的)设施. goto 语句在失败情况下使用, 在事情变坏之前只 对之前已经成功注册的设施进行注销.
另一个选项, 不需要繁多的 goto 语句, 是跟踪已经成功注册的, 并且在任何出错情况下 调用你的模块的清理函数. 清理函数只回卷那些已经成功完成的步骤. 然而这种选择, 需 要更多代码和更多 CPU 时间, 因此在快速途径下, 你仍然依赖于 goto 作为最好的错误恢 复工具.
my_init_function 的返回值, err, 是一个错误码. 在 Linux 内核里, 错误码是负数, 属 于定义于 <linux/errno.h> 的集合. 如果你需要产生你自己的错误码代替你从其他函数得 到的返回值, 你应当包含 <linux/errno.h> 以便使用符号式的返回值, 例如 -ENODEV, - ENOMEM, 等等. 返回适当的错误码总是一个好做法, 因为用户程序能够把它们转变为有意 义的字串, 使用 perror 或者类似的方法.
显然, 模块清理函数必须撤销任何由初始化函数进行的注册, 并且惯例(但常常不是要求的) 是按照注册时相反的顺序注销设施.
void exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull"); unregister_that(ptr2, "skull"); unregister_this(ptr1, "skull"); return;
}
如果你的初始化和清理比处理几项复杂, goto 方法可能变得难于管理, 因为所有的清理代 码必须在初始化函数里重复, 包括几个混合的标号. 有时, 因此, 一种不同的代码排布证 明更成功.
使代码重复最小和所有东西流线化, 你应当做的是无论何时发生错误都从初始化里调用清 理函数. 清理函数接着必须在撤销它的注册前检查每一项的状态. 以最简单的形式, 代码 看起来象这样:
struct something *item1; struct somethingelse *item2; int stuff_ok;
void my_cleanup(void)
{
if (item1)
release_thing(item1); if (item2)
release_thing2(item2); if (stuff_ok)
unregister_stuff();
return;
}
int init my_init(void)
{
int err = -ENOMEM;
item1 = allocate_thing(arguments); item2 = allocate_thing2(arguments2); if (!item2 || !item2)
goto fail;
err = register_stuff(item1, item2); if (!err)
stuff_ok = 1;
else
goto fail;
return 0; /* success */
fail:
}
my_cleanup(); return err;
如这段代码所示, 你也许需要, 也许不要外部的标志来标识初始化步骤的成功, 要依赖你 调用的注册/分配函数的语义. 不管要不要标志, 这种初始化会变得包含大量的项, 常常比 之前展示的技术要好. 注意, 但是, 清理函数当由非退出代码调用时不能标志为 exit, 如同前面的例子.