RT-Thread 自動初始化詳解

目錄

一、前言

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

1.2、使用自動初始化後

二、引入

三、自動初始化原理

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

3.2、自動初始化過程

3.2.1、兩個函數的實現

3.2.2、劃分

3.2.3、示例


一、前言

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

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

 

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

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

//僞代碼

void mian(void)
{
    uart_init();
    led_init();
    ...
    
    
    while(1)
    {
        //func1
        //func2
    }
}

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

 

1.2、使用自動初始化後

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

//這是led文件

void led_init(void)
{
    //省略
}
INIT_APP_EXPORT(led_init)
//這是 main.c 文件
int main(void)
{
    
}

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

 

二、引入

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

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

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

ç³»ç»å¯å¨æµç¨

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

初始化順序 宏接口 描述
1 INIT_BOARD_EXPORT(fn)

非常早期的初始化,此時調度器還未啓動

使用該宏後,fn 將屬於 “board init functions”

2 INIT_PREV_EXPORT(fn)

主要是用於純軟件的初始化、沒有太多依賴的函數

使用該宏後,fn 將屬於 “pre-initialization functions”

3 INIT_DEVICE_EXPORT(fn)

外設驅動初始化相關,比如網卡設備

使用該宏後,fn 將屬於 “device init functions”

4 INIT_COMPONENT_EXPORT(fn)

組件初始化,比如文件系統或者 LWIP

使用該宏後,fn 將屬於 “components init functions”

5 INIT_ENV_EXPORT(fn)

系統環境初始化,比如掛載文件系統

使用該宏後,fn 將屬於 “enviroment init functions”

6 INIT_APP_EXPORT(fn)

應用初始化,比如 GUI 應用

使用該宏後,fn 將屬於 “application init functions”

 

三、自動初始化原理

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

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

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

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

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

而 SECTION(x) 是:

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

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

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

 

舉例:INIT_APP_EXPORT(pin_beep_sample);

//函數pin_beep_sample(),使用INIT_APP_EXPORT()進行自動初始化。

INIT_APP_EXPORT(pin_beep_sample);
= INIT_EXPORT(pin_beep_sample, "6")
= const init_fn_t __rt_init_pin_beep_sample SECTION(".rti_fn.""6") = pin_beep_sample

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

表示把函數 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 段。

 

3.2.1、兩個函數的實現

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

void rt_components_board_init(void)
{
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
#endif
}

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

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

void rt_components_init(void)
{
    const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
#endif
}

非調試模式下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

static int rti_start(void)
{
    return 0;
}
INIT_EXPORT(rti_start, "0");

static int rti_board_start(void)
{
    return 0;
}
INIT_EXPORT(rti_board_start, "0.end");

static int rti_board_end(void)
{
    return 0;
}
INIT_EXPORT(rti_board_end, "1.end");

static int rti_end(void)
{
    return 0;
}
INIT_EXPORT(rti_end, "6.end");

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

段名 函數指針/宏
.rti_fn.0 __rt_init_rti_start
.rti_fn.0.end __rt_init_rti_board_start
.rti_fn.1                     INIT_BOARD_EXPORT(fn) 
.rti_fn.1.end __rt_init_rti_board_end
.rti_fn.2                    INIT_PREV_EXPORT(fn)
.rti_fn.3                    INIT_DEVICE_EXPORT(fn)
.rti_fn.4                    INIT_COMPONENT_EXPORT(fn)
.rti_fn.5                    INIT_ENV_EXPORT(fn)
.rti_fn.6                    INIT_APP_EXPORT(fn)
.rti_fn.6.end __rt_init_rti_end

可以看出,這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);

//函數pin_beep_sample(),使用INIT_APP_EXPORT()進行自動初始化。

INIT_APP_EXPORT(pin_beep_sample);
= INIT_EXPORT(pin_beep_sample, "6")
= const init_fn_t __rt_init_pin_beep_sample SECTION(".rti_fn.""6") = pin_beep_sample

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

表示把函數 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 段中。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章