The Cleanup Function

Every nontrivial module also requires a cleanup function, which unregisters interfaces and returns all resources to the system before the module is removed. This function is defined as: 每个重要的模块还需要一个清理函数,该函数在删除模块之前取消注册接口并将所有资源返回给系统。 该函数定义为:

static void _ _exit cleanup_function(void)

{

    /* Cleanup code here */

}

module_exit(cleanup_function);

The cleanup function has no value to return, so it is declared void. The _ _exit modifier marks the code as being for module unload only (by causing the compiler to place it in a special ELF section). If your module is built directly into the kernel, or if your kernel is configured to disallow the unloading of modules, functions marked _ _exit are simply discarded. For this reason, a function marked _ _exit can be called only at module unload or system shutdown time; any other use is an error. Once again, the module_exit declaration is necessary to enable to kernel to find your cleanup function. 清理函数没有返回值,所以它被声明为 void。 _ _exit 修饰符将代码标记为仅用于模块卸载(通过使编译器将其放置在特殊的 ELF 部分中)。 如果您的模块直接内置到内核中,或者如果您的内核配置为禁止卸载模块,则标记为 _ _exit 的函数将被简单地丢弃。 因此,标记为 _ _exit 的函数只能在模块卸载或系统关闭时调用; 任何其他用途都是错误的。 再一次,module_exit 声明对于启用内核以找到您的清理功能是必要的。

If your module does not define a cleanup function, the kernel does not allow it to be unloaded. 如果你的模块没有定义清理函数,内核不允许它被卸载。

Error Handling During Initialization

One thing you must always bear in mind when registering facilities with the kernel is that the registration could fail. Even the simplest action often requires memory allocation, and the required memory may not be available. So module code must always check return values, and be sure that the requested operations have actually succeeded. 向内核注册设施时必须始终牢记的一件事是注册可能会失败。 即使是最简单的动作也常常需要分配内存,而所需的内存可能不可用。 因此模块代码必须始终检查返回值,并确保请求的操作确实成功。

If any errors occur when you register utilities, the first order of business is to decide whether the module can continue initializing itself anyway. Often, the module can continue to operate after a registration failure, with degraded functionality if necessary. Whenever possible, your module should press forward and provide what capabilities it can after things fail. 如果在注册实用程序时出现任何错误,首先要做的是决定模块是否可以继续初始化自身。 通常,模块可以在注册失败后继续运行,如有必要,功能会降级。 只要有可能,您的模块应该向前推进,并在事情失败后提供它可以提供的功能。

If it turns out that your module simply cannot load after a particular type of failure, you must undo any registration activities performed before the failure. Linux doesn't keep a per-module registry of facilities that have been registered, so the module must back out of everything itself if initialization fails at some point. If you ever fail to unregister what you obtained, the kernel is left in an unstable state; it contains internal pointers to code that no longer exists. In such situations, the only recourse, usually, is to reboot the system. You really do want to take care to do the right thing when an initialization error occurs. 如果事实证明您的模块在特定类型的故障后根本无法加载,您必须撤消在故障之前执行的任何注册活动。 Linux 不保留已注册设施的每个模块注册表,因此如果在某些时候初始化失败,模块必须自行退出所有内容。 如果您未能取消注册您获得的内容,内核将处于不稳定状态; 它包含指向不再存在的代码的内部指针。 在这种情况下,唯一的办法通常是重新启动系统。 当发生初始化错误时,您确实需要注意做正确的事情。

Error recovery is sometimes best handled with the goto statement. We normally hate to use goto, but in our opinion, this is one situation where it is useful. Careful use of goto in error situations can eliminate a great deal of complicated, highly-indented, "structured" logic. Thus, in the kernel, goto is often used as shown here to deal with errors. 错误恢复有时最好使用 goto 语句来处理。 我们通常讨厌使用 goto,但在我们看来,这是一种有用的情况。 在错误情况下仔细使用 goto 可以消除大量复杂的、高度缩进的“结构化”逻辑。 因此,在内核中,经常使用 goto 来处理错误。

The following sample code (using fictitious registration and unregistration functions) behaves correctly if initialization fails at any point: 如果初始化在任何时候失败,以下示例代码(使用虚构的注册和取消注册函数)会正确运行:

int _ _init my_init_function(void)

{

    int err;

    /* registration takes a pointer and a name */

    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; /* success */

  fail_those: unregister_that(ptr2, "skull");

  fail_that: unregister_this(ptr1, "skull");

  fail_this: return err; /* propagate the error */

 }

This code attempts to register three (fictitious) facilities. The goto statement is used in case of failure to cause the unregistration of only the facilities that had been successfully registered before things went bad. 此代码尝试注册三个(虚构的)设施。 goto 语句用于在失败的情况下仅取消注册在事情变坏之前已成功注册的设施。

Another option, requiring no hairy goto statements, is keeping track of what has been successfully registered and calling your module's cleanup function in case of any error. The cleanup function unrolls only the steps that have been successfully accomplished. This alternative, however, requires more code and more CPU time, so in fast paths you still resort to goto as the best error-recovery tool. 另一种选择,不需要杂乱的 goto 语句,是跟踪已成功注册的内容,并在出现任何错误时调用模块的清理函数。 清理函数仅清理已成功完成的步骤。 然而,这种替代方法需要更多的代码和更多的 CPU 时间,因此在快速路径中,您仍然使用 goto 作为最好的错误恢复工具。

The return value of my_init_function, err, is an error code. In the Linux kernel, error codes are negative numbers belonging to the set defined in <linux/errno.h>. If you want to generate your own error codes instead of returning what you get from other functions, you should include <linux/errno.h> in order to use symbolic values such as -ENODEV, -ENOMEM, and so on. It is always good practice to return appropriate error codes, because user programs can turn them to meaningful strings using perror or similar means. my_init_function 的返回值 err 是一个错误代码。 在 Linux 内核中,错误代码是属于 <linux/errno.h> 中定义的集合的负数。 如果您想生成自己的错误代码而不是返回从其他函数获得的错误代码,则应包含 <linux/errno.h> 以使用符号值,例如 -ENODEV、-ENOMEM 等。 返回适当的错误代码始终是一种好习惯,因为用户程序可以使用 perror 或类似方式将它们转换为有意义的字符串。

Obviously, the module cleanup function must undo any registration performed by the initialization function, and it is customary (but not usually mandatory) to unregister facilities in the reverse order used to register them: 显然,模块清理函数必须撤消初始化函数执行的任何注册,并且习惯上(但通常不是强制性的)以用于注册它们的相反顺序取消注册设施:

void _ _exit my_cleanup_function(void)

{

    unregister_those(ptr3, "skull");

    unregister_that(ptr2, "skull");

    unregister_this(ptr1, "skull");

    return;

}

If your initialization and cleanup are more complex than dealing with a few items, the goto approach may become difficult to manage, because all the cleanup code must be repeated within the initialization function, with several labels intermixed. Sometimes, therefore, a different layout of the code proves more successful. 如果您的初始化和清理比处理几个项目更复杂,那么 goto 方法可能会变得难以管理,因为所有清理代码都必须在初始化函数中重复,并且混合了几个标签。 因此,有时,代码的不同布局证明更成功。

What you'd do to minimize code duplication and keep everything streamlined is to call the cleanup function from within the initialization whenever an error occurs. The cleanup function then must check the status of each item before undoing its registration. In its simplest form, the code looks like the following: 为了最大限度地减少代码重复并保持一切精简,您要做的就是在发生错误时从初始化中调用清理函数。 然后,清理函数必须在撤消其注册之前检查每个项目的状态。 最简单的代码如下所示:

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;

}

As shown in this code, you may or may not need external flags to mark success of the initialization step, depending on the semantics of the registration/allocation function you call. Whether or not flags are needed, this kind of initialization scales well to a large number of items and is often better than the technique shown earlier. Note, however, that the cleanup function cannot be marked _ _exit when it is called by nonexit code, as in the previous example. 如此代码所示,您可能需要也可能不需要外部标志来标记初始化步骤的成功,具体取决于您调用的注册/分配函数的语义。 无论是否需要标志,这种初始化都可以很好地扩展到大量项目,并且通常比前面显示的技术更好。 但是请注意,当由非退出代码调用清理函数时,不能将其标记为 _ _exit,如前面的示例所示。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mounter625

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值