一文詳解RT-Thread自動初始化

2021-01-04 電子發燒友

一、前言

在學RT-Thread時,經常能聽到這個詞:自動初始化。用起來也非常容易,一個宏就解決了,但是原理是什麼呢?官網文檔提及到了,

(他們的文檔在這裡:https://www.rt-thread.org/document/site/programming-manual/basic/basic/#rt-thread_3),但是寫的只是概念層面上的,看完後會使用但原理還是不太清楚。之前研究過,今天把它總結下,寫出來分享。

1.1、一般情況的初始化調用

一般情況下,系統中的初始化會這樣做,應該再熟悉不過了:

1//偽代碼 2 3void main(void) 4{ 5    uart_init(); 6    led_init(); 7    ... 8 910    while(1)11    {12        //func113        //func214    }15}

這樣的顯式調用初始化函數,有時可能多達 十幾到幾十 個,看起來非常非常繁雜。但是好像沒啥問題,因為已經看習慣了。

1.2 使用自動初始化後

舉例一個自動初始化的用法如下:

1//這是led.c文件23void led_init(void)4{5    //省略6}7INIT_APP_EXPORT(led_init)1//這是 main.c 文件2int main(void)3{45}

這樣,使用一個宏,初始化函數就會被自動初始化,不用在其他地方顯式調用 led_init() 。代碼瞬間清爽很多。

二、引入

當然也不用擔心一個初始化必須在另一個初始化之前的問題,因為這裡有6個自動初始化等級可供選擇。

我摳了一張RT-Thread官網文檔的圖,該圖是RT-Thread代碼的啟動流程圖,該圖中的藍色方框部分就是自動初始化的6個等級以及初始化的先後順序。從圖中可以看出這6部分的初始化是由函數 rt_components_board_init() 與 rt_components_init() 完成的。

在一開始的例子中, INIT_APP_EXPORT(led_init) 就位於最後一個方框的位置,屬於applications init functions。

那麼其他等級分別對應什麼宏進行初始化的?看下面的表格:

三、自動初始化原理

3.1 6個自動初始化宏的定義

查看源碼,這 6 個宏定義如下:( 不同的段:1 2 3 4 5 6 )

1/* board init routines will be called in board_init() function */ 2#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1") 3 4/* pre/device/component/env/app init routines will be called in init_thread */ 5/* components pre-initialization (pure software initilization) */ 6#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2") 7/* device initialization */ 8#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3") 9/* components initialization (dfs, lwip, ...) */10#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")11/* environment initialization (mount disk, ...) */12#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")13/* appliation initialization (rtgui application etc ...) */14#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

INIT_EXPORT(fn, level) 表示這個函數 fn 現在屬於哪個初始化 level 段, 由 SECTION(".rti_fn."level) 進行定義。

1#define INIT_EXPORT(fn, level)2            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

而 SECTION(x) 是:

1#define SECTION(x)                  __attribute__((section(x)))

__attribute__((section("name"))) :將作用的函數或數據放入指定名為"name"的輸入段中。(在不同的編譯器中實現的方式也有所不同。)

以上就是整個的宏定義作用就是將函數 fn 的地址賦給一個 __rt_init_fn 的指針,然後放入相應 level 的數據段中。所以函數使用自動初始化宏導出後,這些數據段中就會存儲指向各個初始化函數的指針。

舉例:INIT_APP_EXPORT(pin_beep_sample);

1//函數pin_beep_sample(),使用INIT_APP_EXPORT()進行自動初始化。 2 3INIT_APP_EXPORT(pin_beep_sample); 4= INIT_EXPORT(pin_beep_sample, "6") 5= const init_fn_t __rt_init_pin_beep_sample SECTION(".rti_fn.""6") = pin_beep_sample 6 7/* 8表示把函數pin_beep_sample的地址賦值給常量函數指針__rt_init_pin_beep_sample, 9然後放入名稱為".rti_fn.6"的數據段中。10(其中init_fn_t是一個函數指針類型,原型為typedef int (*init_fn_t)(void)。)11*/

表示把函數 pin_beep_sample 的地址賦值給常量函數指針 __rt_init_pin_beep_sample,然後放入名稱為 ".rti_fn.6" 的數據段中。( 其中 init_fn_t 是一個函數指針類型,原型為 typedef int (*init_fn_t)(void)。)

注意被自動初始化的函數類型為:int (*init_fn_t)(void) ,無參,int 返回。

3.2 自動初始化過程

那麼上面提到,在啟動流程中,調用了兩個函數 rt_components_board_init() 與 rt_components_init() 就完成了6部分的初始化。從啟動流程圖中可以看出:rt_components_board_init() 完成了第 1 段, rt_components_init() 完成了第2 到第6 段。

說明:rt_components_board_init()主要board板級的初始化,調度器還未啟動,是在系統起來之前做的初始化。所以在使用board級別的初始化時不要使用系統API,如rt_thread_delay()等。rt_components_init()主要是一些組件的初始化及應用初始化,是在main線程中完成的,當調度器啟動之後,系統啟動開始運行main線程時才會進行的初始化。是線程的運行環境。

3.2.1、兩個函數的實現

1、第一個函數 rt_components_board_init() 的實現:

1void rt_components_board_init(void) 2{ 3    const init_fn_t *fn_ptr; 4 5    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr 

非調試模式下rt_components_board_init():for循環會遍歷位於__rt_init_rti_board_start 到 __rt_init_rti_board_end 之間保存的函數指針,然後依次執行這些函數。

2、第二個函數 rt_components_init() 的實現:

1void rt_components_init(void) 2{ 3    const init_fn_t *fn_ptr; 4 5    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr 

非調試模式下rt_components_init():for循環會遍歷位於__rt_init_rti_board_end 到 __rt_init_rti_end 之間保存的函數指針,然後依次執行這些函數 。

那麼 __rt_init_rti_board_start、__rt_init_rti_board_end、__rt_init_rti_end 是啥?

3.2.2 劃分

在系統中,定義了這幾個空函數:rti_start、rti_board_start、rti_board_end、rti_end。不同的段:0、 0.end 、 1.end 、6.end

1static int rti_start(void) 2{ 3    return 0; 4} 5INIT_EXPORT(rti_start, "0"); 6 7static int rti_board_start(void) 8{ 9    return 0;10}11INIT_EXPORT(rti_board_start, "0.end");1213static int rti_board_end(void)14{15    return 0;16}17INIT_EXPORT(rti_board_end, "1.end");1819static int rti_end(void)20{21    return 0;22}23INIT_EXPORT(rti_end, "6.end");

這幾個函數的導出,加上上面 6 個初始化宏的導出,就有了這樣一個表格:

可以看出,這4個空函數所導出的段中間,包含著這6個初始化宏定義的段,而這6個段中分別包含著各自宏導出函數時的函數指針。

rt_components_board_init() 完成了第 1 段, rt_components_init() 完成了第2 到第6 段。

1、rt_components_board_init() 完成了第 1 段,也就是初始化了由INIT_BOARD_EXPORT(fn) 的初始化的所有函數,也就是__rt_init_rti_board_start 到 __rt_init_rti_board_end 之間的函數指針。

2、rt_components_init() 完成了第2 到第6 段,也就是按順序初始化了由 INIT_PREV_EXPORT(fn) 到 INIT_DEVICE_EXPORT(fn) 到 INIT_COMPONENT_EXPORT(fn)、 INIT_ENV_EXPORT(fn)、 INIT_APP_EXPORT(fn)初始化的所有函數,也就是從 __rt_init_rti_board_end 到 __rt_init_rti_end 之間的函數指針。

所以,當你使用自動初始化導出宏 去初始化一個函數時,是由系統中的這兩個函數進行遍歷函數指針執行的。

3.2.3、示例

還是上面 INIT_APP_EXPORT(pin_beep_sample); 的例子。

舉例:INIT_APP_EXPORT(pin_beep_sample);

1//函數pin_beep_sample(),使用INIT_APP_EXPORT()進行自動初始化。 2 3INIT_APP_EXPORT(pin_beep_sample); 4= INIT_EXPORT(pin_beep_sample, "6") 5= const init_fn_t __rt_init_pin_beep_sample SECTION(".rti_fn.""6") = pin_beep_sample 6 7/* 8表示把函數pin_beep_sample的地址賦值給常量函數指針__rt_init_pin_beep_sample, 9然後放入名稱為".rti_fn.6"的數據段中。10(其中init_fn_t是一個函數指針類型,原型為typedef int (*init_fn_t)(void)。)11*/

表示把函數 pin_beep_sample 的地址賦值給常量函數指針 __rt_init_pin_beep_sample,然後放入名稱為 ".rti_fn.6" 的數據段中。( 其中 init_fn_t 是一個函數指針類型,原型為 typedef int (*init_fn_t)(void)。)

在編譯後的.map文件中可以查看到:

常量函數指針 __rt_init_pin_beep_sample 位於 .rti_fn.6 段中。

打開APP閱讀更多精彩內容

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容圖片侵權或者其他問題,請聯繫本站作侵刪。 侵權投訴

相關焦點

  • rt-thread內核對象標誌(object.flag)總結
    1 前言 在前面介紹rt-thread內核對象時(http://blog.csdn.net/flydream0?viewmode=contents),有介紹到rt-thread內核對象成員flag,但是沒有怎麼具體介紹他的含意,只是把它當做普通標誌來理解,這裡特意將此flag提出來總結一下,這也是這篇博文的目的. 首先,內核對象的flag確實是一標誌,且是8個位的數據,按位來定義的,那麼它到底各個位是怎麼定義的呢?答案是用在不同的內核對象時,它的各位定義會有所不同.
  • 【RT-Thread】線程的基本知識
    動態線程與靜態線程我們的用戶線程有兩種創建方式,一種是靜態線程,另一種是動態線程。創建靜態線程的函數:(void* parameter){    rt_uint32_t i = 0;    rt_kprintf("This is static thread!
  • RT-Thread學習筆記+2.RTT的啟動分析
    然後在rt-thread\bsp\stm32\libraries目錄下,把其他用不到的庫文件也刪掉,只保留了F1的庫文件夾即可。最後強調一下:學習RTT,板子不是關鍵,不論是正點原子、野火、安富萊,還是F1、F4、F7都是可以的,不要糾結板子,最主要的了解RTT的使用方法。
  • RISC-V單片機快速入門03-基於RT_Thread Nano添加控制臺
    前言:上一節,我們完成了GD32VF103在RT_Thread Nano上的移植,本節我們為其增加控制臺輸出功能,以及通過FinSH組件和用戶交互功能。(1) 串口初始化在gd32vf102c_start.c中定義調試串口初始化函數void uart_debug_init(void),同時在gd32vf103c_start.h中聲明。
  • 在rt-thread下實現OTA在線固件更新功能
    Bootloader 在線獲取地址: http://iot.rt-thread.com登陸帳號點擊生成按鈕後,等待大約一分鐘即可通過自動下載或者郵件的方式獲取定製的初始化 FAL由於 FAL 組件會被 ota_downloader 軟體包自動選中,因此直接添加 FAL 組件的初始化代碼即可。
  • RT-Thread教程一之Linux下開發環境及QEMU配置
    >編譯器、調試器我選擇了直接在系統倉庫裡面下載sudo apt install gcc-arm-none-eabi gdb-arm-none-eabi binutils-arm-none-eabi scons qemu-system-arm第一個包提供了編譯鏈工具,第二個是GDB,第三個是工具集,詳見Binutils工具集詳解
  • 《rt-thread驅動框架分析》-i2c驅動
    ③RTT在核心層上,也像pin驅動那樣,封裝了一套API(虛線箭頭),供用戶直接使用。④dev是提供RTT設備驅動框架的統一的API(實現箭頭)。⑤注意的是:模擬I2C驅動到核心層,增加了一層中間層。設備層:設備就是雜七雜八的使用I2C的總線的設備。而這些設備可以選擇使用RTT驅動框架的API,也可以選擇RTT封裝好的API。
  • RT-Thread多線程學習後的總結
    關於多線程的使用和管理,RT-Thread官方提供了比較豐富的文檔作為參考,具體內容可以查看以下連結: https://www.rt-thread.org/document/site/programming-manual/thread/thread/ 本文是對RT-Thread多線程學習後的總結,並嘗試從如圖所示的以下幾個方面進行總結。
  • RISC-V單片機快速入門02-移植RT_Thread Nano
    前言:上一節,我們使用芯來科技Nuclei Studio IDE搭建了裸機開發環境,本節我們開始正式移植RT_Thread系統。document/site/tutorial/nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv/include &34;34;gd32vf103c_start.h&include &34;static rt_thread_t dynamic_thread = RT_NULL;void led_process_thread_entry(void parameter
  • RISC-V單片機快速入門04-基於RT_Thread Nano添加FinSH
    前言:上一節,我們適配了控制臺輸出,可以列印調試信息,本節我們為系統增加FinSH功能,增加FinSH組件後,用戶可輸入命令調試或查看系統信息。一、基礎知識1.FinSH簡介RT-Thread FinSH 是 RT-Thread 的命令行組件(shell),提供一套供用戶在命令行調用的操作接口,主要用於調試或查看系統信息。
  • 玩轉RT-Thread之荔枝派Nano(全志F1C100S)一、新手上路
    github倉庫地址:https://github.com/RT-Thread,碼雲倉庫地址:https://gitee.com/rtthread。Env工具可在RT-Thread官網->「資源」->「下載」頁面下載,下載地址:https://www.rt-thread.org/page/download.html。
  • 並發編程 | 使用ThreadFactory初始化線程池
    所以線程池內的線程並不是線程池對象初始化(new)的時候就創建好的。而是當有任務被提交進來之後才創建的,而創建線程的過程是無法幹預的。如果我們想在每個線程創建時記錄一些日誌,或者推送一些消息那怎麼做?就可以使用ThreadFactory來達到目的,讓線程池內的每個線程都通過ThreadFactory來創建。
  • 基於RT-Thread 使用 wireshark 抓取 HTTPS 數據包
    筆者最近工作中一直用到 HTTPS,但是苦於 wireshark 只能抓取 HTTP 的明文數據包,無法抓取 HTTPS 的數據包,於是就有了這篇文章,在 RT-Thread 系統上,使用 wireshark 抓取 HTTPS 數據包.
  • 國內自主物聯網作業系統 RT-Thread 3.0.1 發布
    在 3.0.1 版本中,USB Stack 得到了極大的增強,在 STM32F4xx 處理器上實現了對應的 USB Device 驅動,以及包括 HID,MSC,甚至是 RNDIS,ECM,WinUSB 等一系列的class driver。
  • 物聯網作業系統一站式開發工具:RT-Thread Studio
    為了解決大家的這些痛點,RT-Thread 官方團隊歷經一年用心打磨,推出了 RT-Thread Studio 集成開發環境(IDE),讓大家告別 ENV,能夠基於一款 IDE 快速的進行 RT-Thread 項目開發。日前,RT-Thread Studio V1.0.2 也已經正式發布了。
  • RT-Thread Studio V1.1 新版本發布
    【官網下載地址】https://www.rt-thread.org/page/studio.html(或點擊文末閱讀原文下載) 由於篇幅有限,更多的功能和使用細節大家可以參閱RT-Thread Studio文檔中心的詳細介紹  : https://www.rt-thread.org
  • Java 並發 - ThreadLocal詳解
    @pdai帶著BAT大廠的面試問題去理解請帶著這些問題繼續後文,會很大程度上幫助你更好的理解相關知識點。@pdai什麼是ThreadLocal? 用來解決什麼問題的?如果當前線程的threadLocals已經初始化(即不為null) 並且存在以當前ThreadLocal對象為Key的值, 則直接返回當前線程要獲取的對象(本例中為Connection);如果當前線程的threadLocals已經初始化(即不為null)但是不存在以當前ThreadLocal對象為Key的的對象, 那麼重新創建一個Connection對象, 並且添加到當前線程的
  • JUC線程池:ThreadPoolExecutor詳解
    @pdai帶著BAT大廠的面試問題去理解請帶著這些問題繼續後文,會很大程度上幫助你更好的理解相關知識點。@pdai為什麼要有線程池?Java是實現和管理線程池有哪些方式? 請簡單舉例如何使用。 Command = 4pool-1-thread-4 End.pool-1-thread-5 End.pool-1-thread-1 End.pool-1-thread-3 End.pool-1-thread-3 Start.
  • RT-Thread AT 組件應用筆記 - 客戶端篇
    AT組件是RT-Thread繼SAL之後的又一重磅發布,解決了不同網絡模塊AT命令之間的差異導致的重複開發的問題,大幅度簡化了MCU+無線模塊方案開發。3.2.1AT Client 配置1.下載RT-Thread 源碼2.下載env 工具3.開啟 env 工具,進入 rt-thread\bsp\stm32f4xx-HAL 目錄,在 env 命令行輸入 menuconfig 進入配置界面配置工程。
  • RT Thread v2.1.0 正式版發布
    下載連結:git打包下載:http://git.oschina.net/rtthread/rt-thread/repository/archive/v2.1.0這個版本經歷的時間比較長,並且原定的一些目標也還未能完成(更全的POSIX支持,包括device fd,socket等在內的select API接口等)。