关于__init和__exit

Last Updated: 2023-03-12 12:38:52 Sunday

-- TOC --

前一篇详解Linux驱动基础使用了一段最简单的内核模块测试代码:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello, world\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "ByeBye, world\n");
}

module_init(hello_init);
module_exit(hello_exit);

本文分析这段代码中的__init__exit

The __init token in the definition may look a little strange; it is a hint to the kernel that the given function is used only at initialization time. The module loader drops the initialization function after the module is loaded, making its memory available for other uses. There is a similar tag __initdata for data used only during initialization. Use of __init and __initdata is optional, but it is worth the trouble. Just be sure not to use them for any function (or data structure) you will be using after initialization completes. You may also encounter __devinit and __devinitdata in the kernel source; these translate to __init and __initdata only if the kernel has not been configured for hotpluggable devices.

当初始化结束后,被__init__initdata修饰的符号,将会从内核空间的内存中清理掉,这些符号将不能再被访问。

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. If your module does not define a cleanup function, the kernel does not allow it to be unloaded.

__exit修饰的接口,只会再unload的时候被调用,如果kernel没有配置支持模块动态加载,这个接口在模块被直接编译进内核时,直接丢弃。如果module没有定义清理函数(用module_exit指定),kernel将不允许其卸载。


使用__init__exit后,.ko文件会存在两个特别的代码段,.text.init.text.exit,注册的init和exit函数,分别存放在这两个section中。如果不使用这两个token,注册的函数都在.text.unlikely段中,但insmod还是能够成功。(由module_init定义)

$ readelf -S hello_mod.ko
There are 49 section headers, starting at offset 0x1a560:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000000  0000000000000000  AX       0     0     1
  [ 2] .init.text        PROGBITS         0000000000000000  00000040
       0000000000000018  0000000000000000  AX       0     0     1
  [ 3] .rela.init.text   RELA             0000000000000000  0000e590
       0000000000000060  0000000000000018   I      46     2     8
  [ 4] .exit.text        PROGBITS         0000000000000000  00000058
       000000000000000c  0000000000000000  AX       0     0     1
  [ 5] .rela.exit.text   RELA             0000000000000000  0000e5f0
       0000000000000030  0000000000000018   I      46     4     8
...
  [22] .exit.data        PROGBITS         0000000000000000  00000288
       0000000000000008  0000000000000000  WA       0     0     8
  [23] .rela.exit.data   RELA             0000000000000000  0000e740
       0000000000000018  0000000000000018   I      46    22     8
  [24] .init.data        PROGBITS         0000000000000000  00000290
       0000000000000008  0000000000000000  WA       0     0     8
  [25] .rela.init.data   RELA             0000000000000000  0000e758
       0000000000000018  0000000000000018   I      46    24     8
...

<linux/init.h>中,有如下定义:

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init      __section(".init.text") __cold  __latent_entropy __noinitretpoline
#define __initdata  __section(".init.data")
#define __initconst __section(".init.rodata")
#define __exitdata  __section(".exit.data")
#define __exit_call __used __section(".exitcall.exit")

这些定义,都使用了编译器的指定section的功能,参考:gcc的__attribute__机制

如果没有module_exit定义清理函数,模块将不允许unload:

$ sudo rmmod hello_mod
rmmod: ERROR: libkmod/libkmod-module.c:799 kmod_module_remove_module() could not remove 'hello_mod': Device or resource busy
rmmod: ERROR: could not remove module hello_mod: Device or resource busy

一次测试时,在init函数中调用了exit,出现如下warning:

WARNING: modpost: /home/xinlin/test/mychar/mychar.o(.text.unlikely+0x82): Section mismatch in reference from the function mychar_init.cold() to the function .exit.text:mychar_exit()
The function mychar_init.cold() references a function in an exit section.
Often the function mychar_exit() has valid usage outside the exit section
and the fix is to remove the __exit annotation of mychar_exit.

以上warning,只使用__exit,没有使用__init。

如果同时使用这两个token,warning如下:

WARNING: modpost: /home/xinlin/test/mychar/mychar.o(.init.text+0xa7): Section mismatch in reference from the function init_module() to the function .exit.text:mychar_exit()
The function __init init_module() references
a function __exit mychar_exit().
This is often seen when error handling in the init function
uses functionality in the exit path.
The fix is often to remove the __exit annotation of
mychar_exit() so it may be used outside an exit section.

warning直接建议remove the __exit annotation,好吧!

本文链接:https://cs.pynote.net/sf/linux/dd/202112021/

-- EOF --

-- MORE --