Unlocking Automatic Module Initialization with Linux Kernel Initcall on STM32
This guide explains how to replicate Linux kernel's initcall mechanism in STM32 projects by using function‑pointer sections, modifying the linker script, and defining ordered initialization macros to replace sequential main‑function calls, enabling modular and loosely‑coupled code execution.
Background
Embedded firmware often places all initialization code inside main(). When the system grows (multiple drivers, peripherals, and subsystems) this linear approach becomes hard to maintain and tightly couples modules. The Linux kernel solves the problem with the initcall mechanism: functions are placed in a dedicated section, the linker collects them, and a generic routine walks the section at boot time and invokes each function in a defined order.
Key Technical Concepts
Program sections ( .bss, .data, custom sections) determine where variables and function pointers reside in the final ELF image.
The GCC attribute __attribute__((section(".initcall"))) (often combined with used) forces a symbol into a user‑defined section.
The linker script must be edited so that the new .initcall section is emitted, aligned, and its start/end symbols are exported.
Implementation Steps
Define a type for initcall entries
typedef void (*initcall_t)(void);Place function pointers in the custom section
static initcall_t __attribute__((used,section(".initcall.0.init")))
low_level_init_ptr = low_level_init;Each macro (see step 5) expands to a similar definition, only the sub‑section index changes.
Modify the linker script Add a section definition and export symbols that mark its boundaries:
.initcall :
{
__initcall_start = .;
KEEP(*(.initcall*))
__initcall_end = .;
} >FLASHAlign the section on an 8‑byte boundary if the target requires it.
Verify the section After linking, run:
readelf -S firmware.elf | grep .initcall
size firmware.elfThe output should show a .initcall segment of the expected size (e.g., 8 bytes) starting at the address reported by the linker (e.g., 0x80005a8 ).
Create ordered init macros Define a set of macros that map to sub‑sections representing the desired execution order:
#define INITCALL_LEVEL(level, fn) \
static initcall_t __attribute__((used,section(".initcall." #level ".init"))) \
__##fn##_ptr = fn;
/* Convenience wrappers */
#define low_level_init(fn) INITCALL_LEVEL(0, fn)
#define arch_init(fn) INITCALL_LEVEL(1, fn)
#define dev_init(fn) INITCALL_LEVEL(2, fn)
#define board_init(fn) INITCALL_LEVEL(3, fn)
#define os_init(fn) INITCALL_LEVEL(4, fn)
#define app_init(fn) INITCALL_LEVEL(5, fn)Using these macros, a developer only writes dev_init(my_i2c_init); and the pointer is automatically placed in .initcall.2.init .
Iterate and invoke the initcalls at runtime Implement a generic runner (often called do_initcalls() ) that walks the range between __initcall_start and __initcall_end :
extern initcall_t __initcall_start;
extern initcall_t __initcall_end;
static void do_initcalls(void)
{
for (initcall_t *p = &__initcall_start; p < &__initcall_end; ++p)
{
(*p)();
}
}Call do_initcalls() early in the boot sequence (e.g., from SystemInit() or the first FreeRTOS task).
Practical Example
The following repository contains a complete STM32 FreeRTOS project that demonstrates the technique:
https://gitee.com/android_life/stm32_freertos_opensource/tree/master/bareos/initcall
Key files: initcall.h – defines the macros and the do_initcalls() function. linker.ld – shows the added .initcall section.
Sample modules ( clock.c, i2c.c, app.c) use low_level_init(), dev_init(), and app_init() to register their start‑up functions.
Running readelf -S on the built ELF confirms the presence of an 8‑byte .initcall region beginning at 0x80005a8. The address corresponds to the SystemInit() function (verified with the size tool).
Benefits
By moving initialization code into ordered sections:
Modules are decoupled – no need to edit a central main() each time a new driver is added.
The boot order is explicit and can be adjusted simply by choosing a different macro level.
The approach scales to large code bases, mirroring the Linux kernel’s initcall infrastructure.
Summary
Using GCC’s section attribute together with a small linker‑script extension allows STM32 (or any bare‑metal) projects to implement a Linux‑style initcall system. Function pointers are placed in a custom .initcall section, the linker exports start/end symbols, and a generic loop invokes each function in the defined order. This pattern reduces coupling, improves maintainability, and provides a clear, extensible initialization flow for embedded firmware.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
