The role of Linux driver __init and __exit

Linux module initialization optimization

When reading the Linux kernel driver source code, you often see that the driver initialization module is modified with "__init" and "__exit". For example, the LED driver supported by the Linux kernel shown below is like this.

static int __init leds_init(void)
{
    
    
    leds_class = class_create("leds");
    ......
    return 0;
}

static void __exit leds_exit(void)
{
    
    
    class_destroy(leds_class);
}

Out of curiosity, what is the purpose of adding "__init" and "__exit" modifications to the driver initialization function? Isn't it a decoration added casually by kernel developers?

1. Function

In fact, "__init" and "__exit" are a space optimization mechanism, which has been supported by the kernel since version v2.1.23. The principle applies to some compilation attributes and compilation instructions provided by the GCC compiler, which will be discussed later. The reason why it is called an optimization mechanism is that after the initialization code is executed, the function containing the initialization code is discarded, and the memory is released and no longer takes up memory.

2. Definition

To know the principle of this optimization, we also need to look at how "__init" and "__exit" are defined. In the "init.h" file of the kernel source code, you can see the following definition:

#define __section(section) __attribute__((__section__(section)))
#define __init __section(".init.text") __cold  __latent_entropy __noinitretpoline

#define __section(section) __attribute__((__section__(section)))
#define __exit __section(".exit.text") __exitused __cold notrace

You can see that "__init" and "__exit" are macro definitions, which are defined as "__section" macro definitions. This "__section" macro definition uses the "__attribute__" instruction provided by the GCC compiler. This instruction is used to set some variables or The attributes of the function, for example, are used to set the section attribute here.

The role of the section attribute:

The section attribute tells the compiler (for Linux, this refers to the GCC compiler) to compile the modified variable or function to a specific location. Note that the specific location mentioned here is not a specific memory location on the physical memory, but is compiled out Within a specific section of the executable file (here refers to a certain section of the ELF executable file, you can view the structure of the Linux ELF executable file).

ELF file segment:

Now let's learn a little about the segment table of the ELF file and see which segments the ELF file contains. The segment table is a structure that saves the basic attributes of various segments in the ELF file. The segment table is the most important structure of ELF besides files. It describes the information of each segment of ELF. The segment structure of an ELF file is determined by the segment table. See the segment table of an ELF executable file below.

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      000000000000002c  0000000000000000  AX       0     0     4
  [ 2] .rela.text        RELA             0000000000000000  00000218     0000000000000078  0000000000000018   I       8     1     8
  [ 3] .data             PROGBITS         0000000000000000  0000006c     0000000000000004  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  00000070     0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000070     0000000000000008  0000000000000000   A       0     0     8
  [ 6] .comment          PROGBITS         0000000000000000  00000078     0000000000000031  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000a9     0000000000000000  0000000000000000           0     0     1
  [ 8] .symtab           SYMTAB           0000000000000000  000000b0     0000000000000150  0000000000000018           9    11     8
  [ 9] .strtab           STRTAB           0000000000000000  00000200     0000000000000017  0000000000000000           0     0     1
  [10] .shstrtab         STRTAB           0000000000000000  00000290     0000000000000052  0000000000000000           0     0     1

Usually the compiler places functions in the ".text" section by default, and variables in the ".data", ".bss" or ".rodata" section according to the status. Using the section attribute allows the compiler to place functions or variables in the specified in the section. So for example: the previous definition of "__init" means that the code it modifies is placed in the ".init.text" section.

Why don’t I see the “.init.text” section in the ELF file above? Because the above ELF file is just an ordinary executable file, the actual kernel divides the segments very carefully because it will locate the corresponding data and code during the running process, so that detailed segmentation will be more convenient to process. Just like all the code modified by "__init" is placed in the ".init.text" section, it will only be called by the kernel during the startup phase. When the initialization is completed, this part of the memory will be released to make full use of the memory. This is The memory management part.

3. How to execute the __init function

After using "__init" modification, all related functions are put together. For example, here all the related functions that need to be run during initialization are placed in the ".init.text" data section.

Finally, all "__init" functions also save a function pointer in the ".initcall.init" section. During initialization, the kernel will call these __init function pointers through these function pointers and release them after the entire initialization is completed. The entire init section (the ".init.text" and ".initcall.init" sections).

Note that the order in which these functions are called during kernel initialization is only related to the order of the function pointers here, and has nothing to do with the order of the functions themselves in the ".init.text" section.

4. __exit

In the same way, "__init" is used to modify functions related to device initialization operations. Then some functions related to canceling device initialization are modified with "__exit". For example, when a driver is uninstalled and no longer used, the memory applied for the driver needs to be released. Then release Memory operations can be performed in the cancel device initialization function.

After being modified with "__exit", the function for canceling the initialization operation is placed in the ".exit.text" section. After the code for canceling the initialization operation is executed, the function containing the cancellation code is discarded. The specific principle is the same as "__init" and will not be repeated here.

Guess you like

Origin blog.csdn.net/jf_52001760/article/details/133270922