在 STM32 上模擬 Linux 自動初始化過程

Linux 中有很多編程思想可以學習,很多大佬把這些思想、機制運用到單片機的編程上,STM32 模擬 Linux kernel 自動初始化流程。

通常我們寫程序都是按照這個套路,一個函數一個函數按照順序邏輯一個一個的執行下去。

如果邏輯非常複雜,涉及的模塊比較多,那麼這種順序執行的代碼就會比較臃腫,各模塊耦合非常緊密。Linux kernel 中,有各種外設驅動,想按照一個順序邏輯執行下去,幾乎是不可能的。

而 kenrel 代碼能有這麼大的代碼量,大而不亂,把各層次,各模塊有效的分離,而大量的代碼又有邏輯的組織在一起,和這個 initcall 有至關重要的作用。

通過模仿這種方式,最後把圖片中 main 函數代碼清空,分離這種邏輯,又實現同樣的功能。

如何能實現這樣的功能了,需要一些背景知識:

1,程序代碼的組織

2,鏈接腳本相關的知識。

3,函數指針的應用。

代碼的組織,如圖片需要知道變量 a,b 及函數指針 f,f2 是存放在程序的哪些段中,可以去看一下這篇 stm32 啓動代碼 實現|C 語言,上述的 a,f 都是存放在 bss 段中,b,f2 是存放在 data 段中,因爲已經給定了初始值,而實現這個 intcall 會把需要自動初始化的數據放到一個自定義的段中去,如. initcall。

如何放到特定的段中了,就需要用到了 attribute((section)) 關鍵字來改變的數據存放段了。

目前的程序編譯出來用到了這些個段,除了. isr_vector 也是添加的,其他都是編譯器默認的。

先加段代碼:

當然這還不夠,還需要告訴連接器(LD) 要把 .initcall 段也鏈接到程序中,所以也需要這段修改。

這段按 8 字節對齊,定義兩個全局變量,及按 0-5 順序的鏈接這些數據,這樣的兩處修改,再來看一下程序各段的情況。

如圖片:

已經多出紅色框框爲. initcalls 段,這段總共是 8 個字節,從 0x80005a8 除開始。

在來看一下具體的這一段的情況,用 readelf 工具。

和上面的 size 工具是匹配的,而綠色框框的地址就是 SystemInit(0x08000231, 小端模式。)

所以通過 attribute 及修改鏈接腳本,就把函數指針變量放到了. initcall 段中。

那麼如何來調用這個函數了,和之前的初始化 data 段數據類似,遍歷這個段,然後取出這個函數地址,然後強制把段中的地址,轉成函數指針,再直接調用即可。

實現的這張圖片,就是從. initcall 段中取出函數地址,然後直接調用,非常容易把函數的地址及這個函數指針變量的地址搞混。

代碼這麼修改,需要自動初始化函數的確是可以調到了,但是每次都寫這麼長長的一段 static initcall_t __ attribute__((__ used__,__ section__(".initcall.0.init"))),就是不舒服. linux kernel 中通過宏來修改。

這個也一樣。

添加 按照程序邏輯順序執行的一些宏

0,low_level_init 比如放始化系統基本時鐘

1,arch_init 比如放 CPU 架構 d 如初始化 NVIC 的一些初始化。

2,dev_init 外設模塊初始化,比 i2c, flash, spi 等。

3,board_init 做具體硬件板及的一些設置。

4,os_init 操作系統的一些設置如,文件系統,網絡協議棧等。

5,app_init 最後跑用戶程序。

把自己的程序也做一下修改,用宏代替。這樣子調用 do_initcalls 就會按照 0,1 - 到 5 的順序執行了。

最後在來看一下 initcall 段:

這樣只要在需要自動初始化函數加上類似於 dev_init(),app_init() 就可以了,就會自動調用到,而不需要 main 函數中一個一個的順序執行。

比如 i2c 控制的初始化放到 dev_init 中,下面掛了很多 i2c 的從設備,只要分別給個從設備用 app_init 初始化就行,即使來了一個新的,也用這 app_init 初始化就行,也不需要更改原來的,高度的分離模塊間的耦合度。

這樣模擬 Linux kenerl 初始化驗證成功,最後上庫。

原文來源於:

https://gitee.com/android_life/stm32_freertos_opensource/tree/master/bareos/initcall

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2i1Dv7NJIFTzTYbfs0F55Q