MicrOS Initialization Framework: Structured Startup for Embedded Systems

One of the challenges of building an embedded operating system is handling system initialization in a clean, modular way. As MicrOS grows, we don’t want board bring-up code, driver setup, and diagnostics all crammed inside main().

That’s why we’ve added a lightweight initialization framework to MicrOS, inspired by how Linux and other RTOSes structure their startup phases.


The Idea

Instead of a hardcoded sequence, MicrOS lets you register init functions with priorities.
The kernel then automatically calls these functions at the right time during boot.

This is powered by linker sections (.preinit_array, .init_array, .fini_array), which GCC and newlib already support. By placing pointers into these sections, we can let the startup code walk through them and invoke each function in order.


The API

We provide simple macros in micros/init.h:

#define MICROS_REGISTER_INIT_FUNCTION(func, priority)
#define MICROS_REGISTER_INIT_EARLY_FUNCTION(func, priority)
#define MICROS_REGISTER_FINISH_FUNCTION(func, priority)
  • INIT → runs during normal initialization
  • INIT_EARLY → runs before almost everything else (clocks, low-level setup)
  • FINISH → runs after main() exits (shutdown hooks, cleanup)

Each function can be given a priority number, so you can order initialization stages.


Example

Here’s a sample application using the framework:

void clock_init(void) {
    printf("--> System clock initialized - simulated\n");
}

void uart_init(void) {
    printf("--> UART initialized - simulated\n");
}

void diagnostics_init(void) {
    printf("--> System initialized successfully\n");
}

void very_early_init_func(void) {
    printf("--> Very early init function in section .init.05\n");
}

void very_late_fini_func(void) {
    printf("--> Very late fini function in section .fini_array\n");
}

/* Register the functions */
MICROS_REGISTER_INIT_EARLY_FUNCTION(clock_init, 0);
MICROS_REGISTER_INIT_FUNCTION(uart_init, 1);
MICROS_REGISTER_INIT_FUNCTION(diagnostics_init, 5);
MICROS_REGISTER_INIT_FUNCTION(very_early_init_func, 55);
MICROS_REGISTER_FINISH_FUNCTION(very_late_fini_func, 0);

int main(void) {
    printf("[main] MicrOS Initialization Framework Example\n");
    printf("[main] System is up and running!\n");
    return 0;
}

Console Output in QEMU

Running this sample on the LM3S board in QEMU:

--> System clock initialized - simulated
--> UART initialized - simulated
--> System initialized successfully
--> Very early init function in section .init.05
[main] MicrOS Initialization Framework Example
[main] System is up and running!
[main] Main function is exiting now.
[boot] Main function returned with code 0
--> Very late fini function in section .fini_array
[boot] System halted after main return

We can clearly see:

  • Early init (clock_init) ran first, before main.
  • Normal init functions (uart_init, diagnostics_init) followed.
  • main() executed.
  • Finally, a finish function ran before halting.

Why This Matters

This framework gives MicrOS a scalable, modular startup sequence:

  • Board files can register their own init hooks.
  • Drivers can self-register without editing core startup code.
  • Cleanup routines can run automatically at shutdown.
  • The system stays predictable: priority values control order.

This makes MicrOS easier to extend as more subsystems appear — from schedulers and filesystems to networking stacks.


What’s Next?

With initialization under control, the next step is to start wiring up driver subsystems (like UART and GPIO) to use this mechanism. That way, enabling or disabling a peripheral becomes just a matter of toggling its init function registration.


👉 You can see the full code in the MicrOS repository and try it out yourself on QEMU or real hardware boards.

Grzegorz Grzęda
Grzegorz Grzęda

At G2Labs, Grzegorz Grzęda develops IoT and embedded platforms while building MicrOS in public and sharing hands-on tutorials.

Articles: 10

Newsletter Updates

Enter your email address below and subscribe to our newsletter