Hello everyone, I would like to recommend an article by my friend, the mixed bag master.
There are many programming ideas to learn in Linux, and many experts apply these ideas and mechanisms to microcontroller programming.
For example: cola_os introduced in the Embedded Mixed Bag Weekly | Issue 4, and the well-known RT-Thread.
Also, the code from the roof sparrow:


This time, I am sharing an article: STM32 Simulating the Linux kernel automatic initialization process, which is also mentioned in the Embedded Mixed Bag Weekly | Issue 4. You can read the original article at the end. Here is the original text:
Usually, when we write programs, we follow this routine, executing one function after another in logical order.

If the logic is very complex and involves multiple modules, then this sequential execution code can become bloated, with tightly coupled modules. In the Linux kernel, there are various peripheral drivers, and it is almost impossible to execute them in a sequential logical order.
The kernel code has such a large amount of code, yet remains organized, effectively separating different layers and modules, while a large amount of code is logically organized together. This is critically important to the initcall mechanism.
By mimicking this approach, we can finally clear the main function code shown in the image, separate this logic, and achieve the same functionality.
To achieve such functionality, some background knowledge is required:
1. Organization of program code
2. Knowledge related to linker scripts.
3. Application of function pointers.

The organization of code, as shown in the image, requires knowing where the variables a, b, and function pointers f, f2 are stored in the program. You can refer to this article on STM32 startup code implementation|C language, where a and f are stored in the bss segment, while b and f2 are stored in the data segment, because they have been given initial values. The initcall mechanism will place the data that needs to be automatically initialized into a custom segment, like .initcall.
To place it into a specific segment, we need to use the attribute((section)) keyword to change the data storage segment.
Currently, the program compiled uses these segments, besides .isr_vector which is also added, the others are defaults from the compiler.

First, add segment code:

Of course, this is not enough; we also need to tell the linker (LD) to link the .initcall segment into the program, so this part also needs modification.

This segment is aligned to 8 bytes, defining two global variables, and linking these data in the order of 0-5. After these two modifications, let’s take a look at the situation of each segment of the program.
As shown in the image:

The red box indicates the .initcalls segment, which is a total of 8 bytes, starting from 0x80005a8.
Now, let’s take a look at the details of this segment using the readelf tool.

And the size tool above matches, while the green box address is SystemInit(0x08000231, little-endian).

Thus, through attributes and modifying the linker script, we have placed the function pointer variable into the .initcall segment.
Now, how to call this function is similar to how we initialized the data segment, by traversing this segment, extracting the function address, forcibly converting the address in the segment into a function pointer, and then calling it directly.


The implementation shown in this image extracts the function address from the .initcall segment and calls it directly, which makes it very easy to confuse the function address with the address of this function pointer variable.
Modifying the code like this allows us to automatically initialize functions, but writing such a long static initcall_t __ attribute__(( __ used__,__ section__(“.initcall.0.init”))) every time is not comfortable. The Linux kernel uses macros to modify this.
This is the same.

Add some macros for executing in logical order of the program:
0. low_level_init, for initializing the basic system clock.
1. arch_init, for CPU architecture initialization, such as initializing NVIC.
2. dev_init, for peripheral module initialization, such as i2c, flash, spi, etc.
3. board_init, for specific hardware board settings.
4. os_init, for operating system settings like file systems, network protocol stacks, etc.
5. app_init, to run user programs last.
Modify your own program using macros instead. This way, calling do_initcalls will execute in the order of 0, 1 to 5.


Finally, let’s take a look at the initcall segment:




Thus, as long as you add something like dev_init(), app_init() to the functions that require automatic initialization, they will be automatically called, without needing to execute them one by one in the main function.
For example, the initialization of i2c control can be placed in dev_init, and many i2c slave devices can be initialized using app_init, even if a new device is added, it can be initialized using app_init without changing the original code, greatly reducing the coupling between modules.
Thus, simulating Linux kernel initialization has been successfully validated, and the library is finally available.
https://gitee.com/android_life/stm32_freertos_opensource/tree/master/bareos/initcall
If you’re interested, you can take a look and follow along.
Copyright Statement:This article is sourced from the internet, freely conveying knowledge, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.