The initcall mechanism
Introduction
As you may understand from the title, this part will cover an interesting and important concept in the Linux kernel which is called initcall
. We already saw definitions like these:
or
in some parts of the Linux kernel. Before we will see how this mechanism is implemented in the Linux kernel, we must know actually what is it and how the Linux kernel uses it. Definitions like these represent a callback function which is called during Linux kernel initialization. Actually the main point of the initcall
mechanism is to determine correct order of the built-in modules and subsystems initialization. For example let's look at the following function:
from the arch/x86/kernel/nmi.c source code file. As we may see it just creates the nmi_longest_ns
debugfs file in the arch_debugfs_dir
directory. Actually, this debugfs
file may be created only after the arch_debugfs_dir
will be created. Creation of this directory occurs during the architecture-specific initialization of the Linux kernel. Actually this directory will be created in the arch_kdebugfs_init
function from the arch/x86/kernel/kdebugfs.c source code file. Note that the arch_kdebugfs_init
function is marked as initcall
too:
The Linux kernel calls all architecture-specific initcalls
before the fs
related initcalls
. So, our nmi_longest_ns
file will be created only after the arch_kdebugfs_dir
directory will be created. Actually, the Linux kernel provides eight levels of main initcalls
:
early
;core
;postcore
;arch
;subsys
;fs
;device
;late
.
All of their names are represented by the initcall_level_names
array which is defined in the init/main.c source code file:
All functions which are marked as initcall
by these identifiers, will be called in the same order presented in the initcall_level_names
array, in other words, at first early initcalls
will be called, then core initcalls
and so forth. From this moment we know a little about initcall
mechanism, so we can start to dive into the source code of the Linux kernel to see how this mechanism is implemented.
Implementation initcall mechanism in the Linux kernel
The Linux kernel provides a set of macros from the include/linux/init.h header file to mark a given function as initcall
. All of these macros are pretty simple:
and as we may see these macros just expand to the call of the __define_initcall
macro from the same header file. Moreover, the __define_initcall
macro takes two arguments:
fn
- callback function which will be called during call ofinitcalls
of the certain level;id
- identifier to identifyinitcall
to prevent error when two the sameinitcalls
point to the same handler.
The implementation of the __define_initcall
macro looks like:
To understand the __define_initcall
macro, first of all let's look at the initcall_t
type. This type is defined in the same header file and it represents pointer to a function which returns integer which will be result of the initcall
:
Now let's return to the __define_initcall
macro. The ## provides ability to concatenate two symbols. In our case, the first line of the __define_initcall
macro produces the definition of a given function, __initcall_<function-name>_<id>
, which is located in the .initcall <id> .init
ELF section and marked with the __user
gcc attribute. If we look at include/asm-generic/vmlinux.lds.h header file, which represents data for the kernel linker script, we will see that all of initcalls
sections will be placed in the .data
section:
and their names are going to be as follows (got from System.map):
The attribute __used
is defined in the include/linux/compiler-gcc.h header file and it expands to the definition of the following gcc
attribute:
which prevents variable defined but not used
warning. The last line of the __define_initcall
macro is:
depends on the CONFIG_LTO
kernel configuration option and just provides stub for the compiler Link time optimization:
In order to prevent any problem when there is no reference to a variable in a module, it will be moved to the end of the program. That's all about the __define_initcall
macro. So, all of the *_initcall
macros will be expanded during compilation of the Linux kernel, and all initcalls
will be placed in their sections and all of them will be available from the .data
section and the Linux kernel will know where to find a certain initcall
to call it during initialization process.
As initcalls
can be called by the Linux kernel, let's look how the Linux kernel does this. This process starts in the do_basic_setup
function from the init/main.c source code file:
which is called during the initialization of the Linux kernel, right after main steps of initialization like memory manager related initialization, CPU
subsystem and others are already finished. The do_initcalls
function just goes through the array of initcall
levels and call the do_initcall_level
function for each level:
The initcall_levels
array is defined in the same source code file and contains pointers to the sections which were defined in the __define_initcall
macro:
If you are interested, you can find these sections in the arch/x86/kernel/vmlinux.lds
linker script which is generated after the Linux kernel compilation:
If you are not familiar with this then you can know more about linkers in the special part of this book.
As we just saw, the do_initcall_level
function takes one parameter - level of initcall
- and does the following two things:
parses the
initcall_command_line
which is copy of usual kernel command line which may contain parameters for modules with theparse_args
function from the kernel/params.c source code file;call the
do_on_initcall
function for each level:
The do_one_initcall
does the main job for us. As we may see, this function takes one parameter which represent initcall
callback function and does the call of the given callback:
Let's try to understand what does the do_one_initcall
function does. First of all we increase preemption counter so that we can check it later to be sure that it is not imbalanced. After this step we can see the call of the initcall_backlist
function which goes over the blacklisted_initcalls
list which stores blacklisted initcalls
and releases the given initcall
if it is located in this list:
This blacklist is filled during early Linux kernel initialization from the Linux kernel command line.
After the blacklisted initcalls
are handled, the next part of code directly calls the initcall
callback:
initcall_debug
variable defines if the call should be handled through the debug codepath (with more information being printed to the kernel log buffer) or not, where the callback will finally be executed. The initcall_debug
variable is defined in the same source code file:
The value of the variable can be set from the kernel commands via the initcall_debug
parameter, as we can read from the documentation of the Linux kernel command line:
And that's true. If we will look at the implementation of the do_one_initcall_debug
function, we will see that it does the same as the do_one_initcall
function, i.e. the do_one_initcall_debug
function calls the given initcall
and prints some information (like the pid of the currently running task, duration of execution of the initcall
and etc.) related to the execution of the given initcall
:
As an initcall
was called by the one of the do_one_initcall
or do_one_initcall_debug
functions, we may see two checks in the end of the do_one_initcall
function. The first one checks the amount of possible __preempt_count_add
and __preempt_count_sub
calls inside of the executed initcall, and if this value is not equal to the previous value of the preemptible counter, we add the preemption imbalance
string to the message buffer and set correct value of the preemptible counter:
The last check the state of local IRQs and if they are disabled, we add the disabled interrupts
strings to log buffer and enable IRQs
for the current processor to make sure that IRQs
are enabled after each initcall
is completed (in case the callback disabled it and didn't enable before exiting):
That's all. In this way the Linux kernel does initialization of many subsystems in a correct order. From now on, we know what is the initcall
mechanism in the Linux kernel. In this part, we covered main general portion of the initcall
mechanism but we left some important concepts. Let's make a short look at these concepts.
First of all, we have missed one level of initcalls
, this is rootfs initcalls
. You can find definition of the rootfs_initcall
in the include/linux/init.h header file along with all similar macros which we saw in this part:
As we may understand from the macro's name, its main purpose is to store callbacks which are related to the rootfs. Besides this goal, it may be useful to initialize other components after initialization related to filesystems level was already done, but before devices related initcalls. For example, the decompression of the initramfs which occurred in the populate_rootfs
function from the init/initramfs.c source code file:
From this place, we may see familiar output:
Besides the rootfs_initcall
level, there are additional console_initcall
, security_initcall
and other secondary initcall
levels. The last thing that we have missed is the set of the *_initcall_sync
levels. Almost each *_initcall
macro that we have seen in this part, has macro companion with the _sync
prefix:
The main goal of these additional levels is to wait for completion of all modules related initialization routines for a certain level.
Another point worthy of mention is the module_init(x)
macro, defined at include/linux/module.h as:
If we follow and check what's the definition of __initcall(x)
at include/linux/init.h we can see that it's being set as an device_initcall
:
With that we can conclude that when a function set as __init
of certain module isn't explicitly added to a specific initcall category, but using module_init()
macro, it is added to device initcall list by default.
That's all.
Conclusion
In this part we saw the important mechanism of the Linux kernel which allows to call a function which depends on the current state of the Linux kernel during its initialization.
If you have questions or suggestions, feel free to ping me in twitter 0xAX, drop me email or just create issue.
Please note that English is not my first language and I am really sorry for any inconvenience. If you found any mistakes please send me PR to linux-insides.
Links
Last updated