Mylène Josserand
July 14, 2020
Reading time:
Initcalls, which serve to call functions during boot, were implemented early on in the development of the Linux Kernel. While they have not required significant changes since version 2.4 released around 2001, tracing support was added by Steven Rostedt in 2018, which can be useful to measure time spent on each initcall to allow for further debugging.
Read on as we take a closer look at initcalls, including their purpose in the Linux kernel, their usage (how to create your function and call one of the definitions), ways to debug them (using initcall_debug
or FTrace), as well as a short overview of their implementation.
As mentioned, the purpose of initcalls are to call functions at different stages during the boot process and are used in many architectures and drivers:
$ git grep _initcall drivers/ arch/ | wc -l 2453
Initcalls are defined as regular functions with the __init
attribute and one of the helpers which will define the type of initcall you want to use. Here is a list of initcalls and their distributions in the code:
The implementation will be explained in the next part of this blog post but keep in mind the main ideas behind initcalls:
If you are not familiar with the ELF (Extensible Linking Format) file format, see this a very nice overview by Corkami:
Here is an example of an initcall which will be executed at the postcore stage.
static int __init mypostcore_init(void) { return 0; } postcore_initcall(mypostcore_init);
You will find many real-world examples in the kernel. For instance, the proc filesystem entry creation for all kernel debug symbols:
atic int __init kallsyms_init(void) { proc_create("kallsyms", 0444, NULL, &kallsyms_proc_ops); return 0; } device_initcall(kallsyms_init);
Using initcalls implies some benefits: your code is more modular and maintainable because we don't need to explicitly pass, store and call function pointers - instead we only mark function as appropriate level initcall and it will be automagically invoked at the appropriate time. However, knowing which one should be used is difficult to distinguish. The level name reflects the order of initcall, in which part initcall will be called, but you have to understand the dependencies and where your function should be executed.
Sometimes, using an initcall is not necessary and using module_init()
can be enough. Notices that these initcalls function can be used only for builtin modules. For modules that can also be loadable, module_init()
is the key.
Initcalls are executed at early stages of the initialization of a module but only for the ones that are statically linked to the kernel. But how about modules than can be loadable or builtin? Many modules (builtin or loadable) don't need to be executed so early because they are not necessary for a device to become usable, for example. In that case, you should use one of the last initcalls levels or module_init()
. Thanks to that, it will save time consumed at bootup and let most important probing functions to be executed earlier.
If a module is compiled builtin (y
in your configuration), module_init()
function will be called during do_initcalls() because this function will be a simple link to device_initcall
function (i.e one of the last initcalls during boot process). Here is the code in include/linux/module.h
:
#ifndef MODULE #define module_init(x) __initcall(x); #define module_exit(x) __exitcall(x);
And __initcall()
is calling device_initcall()
:
$ git grep __initcall include/linux/ [...] include/linux/init.h:#define __initcall(fn) device_initcall(fn) include/linux/module.h:#define module_init(x) __initcall(x);
In case of a loadable module, it will be during the insertion time by using syscalls. All initcalls function will be replaced by a module_init()
one:
#else /* MODULE */ #define early_initcall(fn) module_init(fn) #define core_initcall(fn) module_init(fn) #define postcore_initcall(fn) module_init(fn) #define arch_initcall(fn) module_init(fn) #define subsys_initcall(fn) module_init(fn) #define fs_initcall(fn) module_init(fn) #define rootfs_initcall(fn) module_init(fn) #define device_initcall(fn) module_init(fn) #define late_initcall(fn) module_init(fn) #define console_initcall(fn) module_init(fn) [...]
We will see this use case in the second article that will go deeper in the module_init()
implementation.
To summarize, if you don't have any reasons to execute a function at early stage in booting process, you should use module_init()
if your module can be loaded (which is the case in most of non-core drivers).
A debug command-line parameter exists to print 2 messages while executing all initcalls functions. For that, you should use initcall_debug
introduced in 2.5.67 in the command-line.
[...] [ 0.040325] calling init_mmap_min_addr+0x0/0x20 @ 1 [ 0.040345] initcall init_mmap_min_addr+0x0/0x20 returned 0 after 0 usecs [ 0.040357] calling net_ns_init+0x0/0x140 @ 1 [ 0.040635] initcall net_ns_init+0x0/0x140 returned 0 after 0 usecs [ 0.040740] calling vfp_init+0x0/0x1d0 @ 1 [ 0.040758] VFP support v0.3: implementor 41 architecture 2 part 30 variant 7 rev 5 [ 0.040777] initcall vfp_init+0x0/0x1d0 returned 0 after 0 usecs [ 0.040786] calling ptrace_break_init+0x0/0x2c @ 1 [ 0.040796] initcall ptrace_break_init+0x0/0x2c returned 0 after 0 usecs [ 0.040808] calling register_cpufreq_notifier+0x0/0x10 @ 1 [ 0.040819] initcall register_cpufreq_notifier+0x0/0x10 returned 0 after 0 usecs [ 0.040829] calling v6_userpage_init+0x0/0x8 @ 1 [ 0.040839] initcall v6_userpage_init+0x0/0x8 returned 0 after 0 usecs [ 0.040849] calling cpu_hotplug_pm_sync_init+0x0/0x18 @ 1 [ 0.040860] initcall cpu_hotplug_pm_sync_init+0x0/0x18 returned 0 after 0 usecs [ 0.040869] calling alloc_frozen_cpus+0x0/0x8 @ 1 [ 0.040878] initcall alloc_frozen_cpus+0x0/0x8 returned 0 after 0 usecs [ 0.040887] calling wq_sysfs_init+0x0/0x30 @ 1 [ 0.040973] initcall wq_sysfs_init+0x0/0x30 returned 0 after 0 usecs [ 0.040983] calling ksysfs_init+0x0/0xa4 @ 1 [ 0.041026] initcall ksysfs_init+0x0/0xa4 returned 0 after 0 usecs [ 0.041037] calling pm_init+0x0/0x7c @ 1 [ 0.041107] initcall pm_init+0x0/0x7c returned 0 after 0 usecs [ 0.041119] calling rcu_set_runtime_mode+0x0/0x18 @ 1 [ 0.041130] initcall rcu_set_runtime_mode+0x0/0x18 returned 0 after 0 usecs [...]
This is a great debug parameters to detect which initcalls are taking too much time, particularly in case of boot-time improvements. However, as these debug logs are directly printed on the console and it is printing messages for all initcalls, it can be difficult to retrieve a particular information in this verbose log without having our great binary: grep
.
This is why ftrace has been introducted on initcalls. If you are not familiar with FTrace, here is a really nice video from Steven Rostedt.
Here is a little example of how to use it in case of initcalls-tracing:
# mount -t debugfs nodev /sys/kernel/debug # cat /sys/kernel/debug/tracing/available_events | grep initcall initcall:initcall_finish initcall:initcall_start initcall:initcall_level
initcall_level
, initcall_start
and initcall_finish events
:
# cat /proc/cmdline console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait trace_event=initcall:initcall_level,initcall:initcall_start,initcall:initcall_finish
trace
entry on debugfs:
# mount -t debugfs nodev /sys/kernel/debug # cat /sys/kernel/debug/tracing/trace # tracer: nop # # entries-in-buffer/entries-written: 1090/1090 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | -0 [000] .... 0.000125: initcall_level: level=console -0 [000] .... 0.000136: initcall_start: func=con_init+0x0/0x220 -0 [000] .... 0.000232: initcall_finish: func=con_init+0x0/0x220 ret=0 -0 [000] .... 0.000235: initcall_start: func=univ8250_console_init+0x0/0x3c -0 [000] .... 0.000246: initcall_finish: func=univ8250_console_init+0x0/0x3c ret=0 swapper/0-1 [000] .... 0.002016: initcall_level: level=early swapper/0-1 [000] .... 0.002026: initcall_start: func=trace_init_flags_sys_exit+0x0/0x24 swapper/0-1 [000] .... 0.002029: initcall_finish: func=trace_init_flags_sys_exit+0x0/0x24 ret=0 swapper/0-1 [000] .... 0.002031: initcall_start: func=trace_init_flags_sys_enter+0x0/0x24 swapper/0-1 [000] .... 0.002032: initcall_finish: func=trace_init_flags_sys_enter+0x0/0x24 ret=0 swapper/0-1 [000] .... 0.002034: initcall_start: func=cpu_suspend_alloc_sp+0x0/0xac swapper/0-1 [000] .... 0.002037: initcall_finish: func=cpu_suspend_alloc_sp+0x0/0xac ret=0 swapper/0-1 [000] .... 0.002038: initcall_start: func=init_static_idmap+0x0/0x100 swapper/0-1 [000] .... 0.002084: initcall_finish: func=init_static_idmap+0x0/0x100 ret=0 swapper/0-1 [000] .... 0.002088: initcall_start: func=sunxi_mc_smp_init+0x0/0x3a8 swapper/0-1 [000] .... 0.002102: initcall_finish: func=sunxi_mc_smp_init+0x0/0x3a8 ret=-19 swapper/0-1 [000] .... 0.002104: initcall_start: func=spawn_ksoftirqd+0x0/0x58 swapper/0-1 [000] .... 0.002213: initcall_finish: func=spawn_ksoftirqd+0x0/0x58 ret=0 swapper/0-1 [000] .... 0.002217: initcall_start: func=migration_init+0x0/0x54 swapper/0-1 [000] .... 0.002219: initcall_finish: func=migration_init+0x0/0x54 ret=0 swapper/0-1 [000] .... 0.002220: initcall_start: func=check_cpu_stall_init+0x0/0x28 swapper/0-1 [000] .... 0.002223: initcall_finish: func=check_cpu_stall_init+0x0/0x28 ret=0 swapper/0-1 [000] .... 0.002224: initcall_start: func=srcu_bootup_announce+0x0/0x44 swapper/0-1 [000] .... 0.002235: initcall_finish: func=srcu_bootup_announce+0x0/0x44 ret=0 swapper/0-1 [000] .... 0.002236: initcall_start: func=rcu_spawn_gp_kthread+0x0/0x150 swapper/0-1 [000] .... 0.002299: initcall_finish: func=rcu_spawn_gp_kthread+0x0/0x150 ret=0 swapper/0-1 [000] .... 0.002302: initcall_start: func=cpu_stop_init+0x0/0xbc swapper/0-1 [000] .n.. 0.002384: initcall_finish: func=cpu_stop_init+0x0/0xbc ret=0 swapper/0-1 [000] .n.. 0.002387: initcall_start: func=init_events+0x0/0x70 swapper/0-1 [000] .n.. 0.002403: initcall_finish: func=init_events+0x0/0x70 ret=0 swapper/0-1 [000] .n.. 0.002404: initcall_start: func=init_trace_printk+0x0/0xc [...]
Once again, FTrace logging allows to have great information to detect portions of code whose execution take a lot of time.
Now we know what initcalls are for, let's look at how they are implemented in Linux Kernel's code. One part of the implementation of initcalls is in include/linux/init.h
.
#define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define postcore_initcall(fn) __define_initcall(fn, 2) #define arch_initcall(fn) __define_initcall(fn, 3) #define subsys_initcall(fn) __define_initcall(fn, 4) #define fs_initcall(fn) __define_initcall(fn, 5) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define late_initcall(fn) __define_initcall(fn, 7)
Initcalls are defined thanks to a generic __define_initcall()
with two arguments:
id
which is a way to order the initcalls. The ordering is not according to a number because if you look closer at this argument, its value is equals to rootfs
for rootfs_initcall()
. All initcalls defined using postcore_initcall
will have a second argument of 2. This is the main difference between all initcalls: the ID (that can be both number and string, see rootfs
). It will be different depending on the type of initcall used.Note: Let’s focus on postcore_initcall
in the rest of this article.
#define postcore_initcall(fn) __define_initcall(fn, 2)
Let’s look at this definition in the kernel source:
You have our dummy example: a simple function that is defined as a postcore initcall. The postcore_initcall
definition is calling the __define_initcall()
with its ID: 2. The __define_initcall(fn, id)
is calling another define_initcall()
with an additional arguments: __sec
. For all initcalls, it will be equals to .initcall
+ the id of the initcall. For our postcore initcall, the last parameter will be .initcall2
.
The last __define_initcall()
will look like this (code and expanded versions):
Too many colors, right?
Let's take a break for now and keep the explanation of that part for the next article!
In this first blog post, we have seen an overview of initcalls:
core_initcall
, postcore_initcall
, fs_initcall
, device_initcall
, ...);module_init()
function and the difference (or not...) with initcalls;initcall_debug
and FTrace. They are very useful in case the boot-time must be as fast as possible;__define_initcall()
.The next part will go deeper into the implementation of initcalls, particularly about this colorful __device_initcall()
macro.
08/10/2024
Having multiple developers work on pre-merge testing distributes the process and ensures that every contribution is rigorously tested before…
15/08/2024
After rigorous debugging, a new unit testing framework was added to the backend compiler for NVK. This is a walkthrough of the steps taken…
01/08/2024
We're reflecting on the steps taken as we continually seek to improve Linux kernel integration. This will include more detail about the…
27/06/2024
With each board running a mainline-first Linux software stack and tested in a CI loop with the LAVA test framework, the Farm showcased Collabora's…
26/06/2024
WirePlumber 0.5 arrived recently with many new and essential features including the Smart Filter Policy, enabling audio filters to automatically…
12/06/2024
Part 3 of the cmtp-responder series with a focus on USB gadgets explores several new elements including a unified build environment with…
Comments (0)
Add a Comment