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.