0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

RT-Thread自動初始化詳解

嵌入式大雜燴 ? 來源:嵌入式大雜燴 ? 作者:嵌入式大雜燴 ? 2022-06-25 21:38 ? 次閱讀

我們知道,在寫裸機程序時,當我們完成硬件初始化后,就需要在主函數(shù)中進行調(diào)用。當我們使用RT-Thread后,完全不需要這樣做了,我們可以將硬件等自動初始化。RT-Thread自動初始化機制是指初始化函數(shù)不需要被顯式調(diào)用,只需要在函數(shù)定義處通過宏定義的方式進行申明,就會在系統(tǒng)啟動過程中被執(zhí)行,非常的方便。

1 普通初始化

前面也講了,我們在寫單片機的程序時,需要對硬件進行初始化操作,我們這里還是以LED為例。需要對LED的GPIO進行初始化后才能進一步操作。

int main(void)
{
  rt_err_t rst; 
  /* LED初始化 */
  LED_GPIO_Config();  
  
  rst = rt_thread_init(&led_thread,
  "ledshine",
  led_thread_entry,
  RT_NULL,
  &led_thread_stack[0],
  sizeof(led_thread_stack),
  RT_THREAD_PRIORITY_MAX-2,
  20);
  if(rst == RT_EOK)
  {
  rt_thread_startup(&led_thread);
  }
}

上述代碼很簡單,就是在main()函數(shù)中對LED的GPIO進行初始化,也就是調(diào)用了LED_GPIO_Config() 函數(shù),而針對RT-Thread系統(tǒng),我們在需要初始化的地方進行初始化即可,無需在main()函數(shù)或者board.c中初始化了。

2 RT-Thread初始化流程

要想搞清楚RT-Thread的自動初始化流程,那么必須的了解RT-Thread初始化流程,這一部分前文也就有講,官方也有,我們還是再來復(fù)習下。

RT-Thread支持多種平臺和多種編譯器。RT-Thread啟動代碼統(tǒng)一入口為 rtthread_startup(),芯片啟動文件在完成必要工作(如初始化時鐘、配置中斷向量表、初始化堆棧等)后,跳轉(zhuǎn)至 RT-Thread的啟動入口中,最后進入用戶入口 main()。RT-Thread的啟動流程如下:

1.全局關(guān)中斷,初始化與系統(tǒng)相關(guān)的硬件。

2.打印系統(tǒng)版本信息,初始化系統(tǒng)內(nèi)核對象(如定時器、調(diào)度器)。

3.初始化用戶 main線程(同時會初始化線程棧),在 main線程中對各類模塊依次進行初始化。

4.初始化軟件定時器線程、初始化空閑線程。

5.啟動調(diào)度器,系統(tǒng)切換到第一個線程開始運行(如 main線程),并打開全局中斷。

pYYBAGK25hOANpYRAAERUWXJgTY108.png

在圖中標出顏色的部分需要用戶特別注意(黃色表示 libcpu移植相關(guān)的內(nèi)容,綠色部分表示板級移植相關(guān)的內(nèi)容)。

3 RT-Thread自動初始化原理

既然是初始化,我們這里中的重點關(guān)注初始化過程,重新整理RT-Thread初始化如下圖所示:

poYBAGK25iyATmbSAAEolP_gziE566.png

在系統(tǒng)啟動流程圖中,有兩個函數(shù):rt_components_board_init() 與 rt_components_init(),其后的帶底色方框內(nèi)部的函數(shù)表示被自動初始化的函數(shù),其中:

“board init functions”為所有通過 INIT_BOARD_EXPORT(fn)申明的初始化函數(shù)。

“pre-initialization functions”為所有通過 INIT_PREV_EXPORT(fn)申明的初始化函數(shù)。

“device init functions”為所有通過 INIT_DEVICE_EXPORT(fn)申明的初始化函數(shù)。

“components init functions”為所有通過 INIT_COMPONENT_EXPORT(fn)申明的初始化函數(shù)。

“enviroment init functions”為所有通過 INIT_ENV_EXPORT(fn)申明的初始化函數(shù)。

“application init functions”為所有通過 INIT_APP_EXPORT(fn)申明的初始化函數(shù)。

rt_components_board_init() 函數(shù)執(zhí)行的比較早,主要初始化相關(guān)硬件環(huán)境,執(zhí)行這個函數(shù)時將會遍歷通過 INIT_BOARD_EXPORT(fn)申明的初始化函數(shù)表,并調(diào)用各個函數(shù)。

rt_components_init() 函數(shù)會在操作系統(tǒng)運行起來之后創(chuàng)建的 main線程里被調(diào)用執(zhí)行,這個時候硬件環(huán)境和操作系統(tǒng)已經(jīng)初始化完成,可以執(zhí)行應(yīng)用相關(guān)代碼。rt_components_init() 函數(shù)會遍歷通過剩下的其他幾個宏申明的初始化函數(shù)表。

RT-Thread的自動初始化機制使用了自定義 RTI符號段,將需要在啟動時進行初始化的函數(shù)指針放到了該段中,形成一張初始化函數(shù)表,在系統(tǒng)啟動過程中會遍歷該表,并調(diào)用表中的函數(shù),達到自動初始化的目的。

自動初始化功能的宏接口定義詳細描述如下表所示:

初始化順序 宏接口 描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此時調(diào)度器還未啟動
2 INIT_PREV_EXPORT(fn) 主要是用于純軟件的初始化、沒有太多依賴的函數(shù)
3 INIT_DEVICE_EXPORT(fn) 外設(shè)驅(qū)動初始化相關(guān),比如網(wǎng)卡設(shè)備
4 INIT_COMPONENT_EXPORT(fn) 組件初始化,比如文件系統(tǒng)或者 LWIP
5 INIT_ENV_EXPORT(fn) 系統(tǒng)環(huán)境初始化,比如掛載文件系統(tǒng)
6 INIT_APP_EXPORT(fn) 應(yīng)用初始化,比如 GUI應(yīng)用

初始化函數(shù)主動通過這些宏接口進行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),鏈接器會自動收集所有被申明的初始化函數(shù),放到 RTI符號段中,該符號段位于內(nèi)存分布的 RO段中,該 RTI符號段中的所有函數(shù)在系統(tǒng)初始化時會被自動調(diào)用。

好了,介紹性文字我就不貼了,下面直接看源代碼進一步分析。前文說過,在RT-Thread的啟動流程中,調(diào)用了兩個函數(shù) rt_components_board_init()與 rt_components_init()就完成了上述6個部分的初始化工作。從初始化啟動流程圖中我們可以看出: rt_components_board_init()完成了第 1部分工作, rt_components_init()完成了第2-6部分的工作。那么接下來我們先看這兩個函數(shù)源代碼。


/**
 * RT-Thread Components Initialization for board
 */
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;
    for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
   }
#else
    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-Thread Components Initialization
 */
void rt_components_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;

    rt_kprintf("do components initialization.\n");
    for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
    }
#else
    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_board_init() 與 rt_components_init()函數(shù)在components.c文件中實現(xiàn)。

可以看到兩個函數(shù)在非調(diào)試模式下都是通過for循環(huán)會遍歷位于__rt_init_rti_board_start 到 __rt_init_rti_board_end以及__rt_init_rti_board_end 到 __rt_init_rti_end之間保存的函數(shù)指針,然后依次執(zhí)行這些函數(shù)。那么接下來我們看看上述函數(shù)指針。我們先編譯下,找到.map文件,我們在在文件中搜索上述的函數(shù)指針。

poYBAGK25lCAF7g2AAIZwqwwSs0219.png

又問會問,找到又能怎樣呢?怎么和上述的6個宏定義對應(yīng)起來呢?不急哈,慢慢來,我們先找到上述6個宏定義又是如何實現(xiàn)的,找到啦,在rtdef.h中。

poYBAGK25mKAcQwYAAKv6PeRkr0395.png

宏定義又是通過INIT_EXPORT宏函數(shù)定義的,那么INIT_EXPORT又是如何實現(xiàn)的呢?

#ifdef _MSC_VER /* we do not support MS VC++ compiler */
    #define INIT_EXPORT(fn, level)
#else
    #if RT_DEBUG_INIT
        struct rt_init_desc
        {
            const char* fn_name;
            const init_fn_t fn;
        };
        #define INIT_EXPORT(fn, level)                                                     \
            const char __rti_##fn##_name[] = #fn;                                           \
            RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
            { __rti_##fn##_name, fn};
    #else
        #define INIT_EXPORT(fn, level)                                                      \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
    #endif
#endif

【注】上述代碼在rtdef.h中實現(xiàn)。

上述代碼最關(guān)鍵的代碼是:

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

這又是啥,看不懂啊,不急哈,我們先看看init_fn_t是啥?其定義如下:

#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);

這就是一個函數(shù)指針類型,其實就是個指針。那么SECTION又是啥呢?其定義如下:

poYBAGK25pCAErH7AAFfuVXN8u8707.png

__attribute__((used))表示這個標記這個東西是使用過的,避免出現(xiàn)如: warning: #177-D: variable "a" was declared but never referenced的警告。

在GCC的宏中,##后面跟變量名。__attribute__((section(x)))則表示fn被放置于指定段中。是不是還是一頭霧水,不急,我們再看看一些代碼或許你就明白了。

/*
 * Components Initialization will initialize some driver and components as following
 * order:
 * rti_start        --> 0
 * BOARD_EXPORT     --> 1
 * rti_board_end    --> 1.end
 *
 * DEVICE_EXPORT    --> 2
 * COMPONENT_EXPORT --> 3
 * FS_EXPORT        --> 4
 * ENV_EXPORT       --> 5
 * APP_EXPORT       --> 6
 *
 * rti_end          --> 6.end
 *
 * These automatically initialization, the driver or component initial function must
 * be defined with:
 * INIT_BOARD_EXPORT(fn);
 * INIT_DEVICE_EXPORT(fn);
 * ...
 * INIT_APP_EXPORT(fn);
 * etc.
 */
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");

【注】以上代碼再在components.c文件中實現(xiàn)。

好了,我們結(jié)合上述代碼和6個宏定義,以及.map文件。

pYYBAGK25rOAR3UiAAJaDX-u5M0749.png

上圖中框選的第一行是一個Section,叫做.rti_fn.0,這個內(nèi)容實際是我們通過INIT_EXPORT(rti_start, "0");完成的,我們把函數(shù)rti_start改名為__rt_init_rti_start,存入.rti_fn.0這個地方。同樣的,INIT_EXPORT(rti_board_start, "0.end");、INIT_EXPORT(rti_board_end, "1.end");、INIT_EXPORT(rti_end, "6.end");也是這里插入的。下圖就是我們插入的位置。

pYYBAGK25riADKagAAI28bnZbrg840.png

這下是不是很明白,當然啦,多看幾遍,還是很好理解的。

4 RT-Thread自動初始化實例

前文理論講了很多,源代碼也分析,估計初學(xué)者還很蒙,沒關(guān)系,慢慢理解,我們先看個例子,先用起來,后面再慢慢理解。還會是LED的例子,前文已近給出了普通初始化方式,下面我們看看如何使用自動化初始化。

/**
 * @brief 初始化LED的GPIO
 * @param None
 * @retval None
 */
int LED_GPIO_Config(void)
{ 
  /*定義一個GPIO_InitTypeDef類型的結(jié)構(gòu)體*/
  GPIO_InitTypeDef GPIO_InitStructure;

  /*開啟LED的外設(shè)時鐘*/
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE); 

  /*設(shè)置IO口*/   
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //設(shè)置引腳模式為通用推挽輸出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //設(shè)置引腳速率為50MHz 

  /*調(diào)用庫函數(shù),初始化GPIOB0*/
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //選擇要控制的GPIOB引腳 
  GPIO_Init(GPIOB, &GPIO_InitStructure); 
    
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*選擇要控制的引腳*/ 
  GPIO_Init(GPIOG, &GPIO_InitStructure); 
   
  /* 開啟所有l(wèi)ed燈 */
  GPIO_SetBits(GPIOB, GPIO_Pin_0);
  GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);  
  
   return 0;
}

/* LED初始化 */
INIT_BOARD_EXPORT(LED_GPIO_Config); 

我們注意最后一行代碼,這里使用INIT_BOARD_EXPORT宏定義進行初始化,其初始化時間最早,值得注意的是,上述6個宏定義修飾的函數(shù)返回值都是int,最好將返回值改為int。

如果LED配置正確,其實驗現(xiàn)象和普通初始化方式?jīng)]有任何區(qū)別,接下來我們再來看看.map文件有何變化,當然我說的是自動初始化部分。

pYYBAGK25uOARarPAAK0mM8C1Xw687.pngpYYBAGK25veAZhEsAAIrD_z92Ds321.png

我們可以看到多了LED初始化的信息,也說明我們前文分析的是合理的。

5 總結(jié)

好了,關(guān)于自動出初始化就講完了,我估計初學(xué)者很蒙,沒關(guān)系,我們再來總結(jié)下,還是先看看RT-Thread初始化過程,如下圖所示:

poYBAGK25iyATmbSAAEolP_gziE566.png

rt_components_board_init() 與 rt_components_init()負責初始化,其中帶底色方框內(nèi)部的函數(shù)表示被自動初始化的函數(shù)。這部分應(yīng)該沒是啥問題。關(guān)鍵是如何將初始化函數(shù)和6個自動初始化宏定義聯(lián)系起來這就有點燒腦子,我整理了一張關(guān)系圖,如下圖所示:

poYBAGK25xOAZEjcAAGLam2avME478.png

結(jié)合前文的講解,再結(jié)合上述兩張圖,我不想在贅述了,自行理解去吧。

參考地址:

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 初始化
    +關(guān)注

    關(guān)注

    0

    文章

    49

    瀏覽量

    11814
  • RT-Thread
    +關(guān)注

    關(guān)注

    31

    文章

    1261

    瀏覽量

    39837
收藏 人收藏

    評論

    相關(guān)推薦

    RT-Thread qemu mps2-an385 bsp移植制作 :系統(tǒng)運行篇

    前面已經(jīng)讓 RT-Thread 進入了 entry 入口函數(shù),并且 調(diào)整 鏈接腳本,自動初始化與 MSH shell 的符號已經(jīng)預(yù)留, 進入了 RT-Thread
    的頭像 發(fā)表于 11-14 12:27 ?758次閱讀
    <b class='flag-5'>RT-Thread</b> qemu mps2-an385 bsp移植制作 :系統(tǒng)運行篇

    RT-thread初始化過程是怎樣進行的

    RT-thread初始化過程是怎樣進行的?擴展補丁Sub和super的作用是什么?如何去使用它們呢?
    發(fā)表于 11-29 07:42

    如何對RT-Thread系統(tǒng)進行初始化

    RT-Thread是如何啟動的?如何對RT-Thread系統(tǒng)進行初始化呢?
    發(fā)表于 11-30 07:54

    為什么RT-Thread要采用這種復(fù)雜的方式來進行自動初始化操作呢

    在分析之前首先查閱 RT-Thread 的官方文檔RT-Thread 自動初始化機制,根據(jù)官方文檔的講述在 RTT 源碼中一共使用了 6 中順序的
    發(fā)表于 04-06 17:49

    RT-Thread自動初始化機制簡介

    RT-Thread 的時鐘管理以時鐘節(jié)拍為基礎(chǔ),時鐘節(jié)拍是 RT-Thread 操作系統(tǒng)中最小的RT-Thread 自動初始化機制時鐘單位。
    發(fā)表于 04-06 18:08

    RT-Thread系統(tǒng)自動初始化機制簡介

    RT-Thread 自動初始化機制1、自動初始化機制簡介在系統(tǒng)啟動流程圖中,有兩個函數(shù):rt_c
    發(fā)表于 04-12 17:43

    【原創(chuàng)精選】RT-Thread征文精選技術(shù)文章合集

    。RT-Thread自動初始化詳解GD32 RISC-V系列 BSP框架制作與移植GD32407V-START開發(fā)板的BSP框架制作與移植基于Select/Poll實現(xiàn)并發(fā)服務(wù)器(一)
    發(fā)表于 07-26 14:56

    RT-Thread系統(tǒng)初始化與啟動流程詳細描述

    系統(tǒng)定時器線程voidrt_application_init ()創(chuàng)建用戶線程詳細描述RT-Thread 的啟動流程RT-Thread 的啟動流程,大致可以分為四個部分:(1)初始化與系統(tǒng)相關(guān)的硬件
    發(fā)表于 08-25 15:15

    RT-Thread自動初始化原理分析

    ;}這里我們直接就可以使用 printf 進行打印,而沒有進行一些其它的初始化,參考這個思路引出了 RT-Thread自動初始化機制。
    發(fā)表于 12-05 14:17

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

    在學(xué)RT-Thread時,經(jīng)常能聽到這個詞:自動初始化。用起來也非常容易,一個宏就解決了,但是原理是什么呢?
    的頭像 發(fā)表于 07-21 10:17 ?7515次閱讀
    一文<b class='flag-5'>詳解</b><b class='flag-5'>RT-Thread</b><b class='flag-5'>自動</b><b class='flag-5'>初始化</b>

    RT-Thread全球技術(shù)大會:如何使用組件以及自動初始化流程

    RT-Thread全球技術(shù)大會:如何使用組件和自動初始化流程 ? ? ? ? ? 審核編輯:彭靜
    的頭像 發(fā)表于 05-27 15:16 ?890次閱讀
    <b class='flag-5'>RT-Thread</b>全球技術(shù)大會:如何使用組件以及<b class='flag-5'>自動</b><b class='flag-5'>初始化</b>流程

    RT-Thread自動初始化機制

    ??在分析之前首先查閱 RT-Thread 的官方文檔 [RT-Thread 自動初始化機制](https://www.rt-thread.
    的頭像 發(fā)表于 06-17 08:52 ?2504次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>自動</b><b class='flag-5'>初始化</b>機制

    rt-thread 優(yōu)化系列(六)啟動流程重構(gòu)

    去年此時,筆者剛接觸 rt-thread 的時候,被它的初始化過程深深折服了。第一次打開一個 rt-thread 的項目,竟然沒找到多線程在哪兒初始化的,"main" 函數(shù)里沒有!
    的頭像 發(fā)表于 07-04 15:30 ?1692次閱讀
    <b class='flag-5'>rt-thread</b> 優(yōu)化系列(六)啟動流程重構(gòu)

    rt-thread線程棧初始化參數(shù)分析

    RT-Thread 在線程初始化的代碼內(nèi)有一段初始化線程堆棧的代碼
    的頭像 發(fā)表于 08-14 16:50 ?1564次閱讀
    <b class='flag-5'>rt-thread</b>線程棧<b class='flag-5'>初始化</b>參數(shù)分析

    RT-Thread使用經(jīng)驗分享:鏈表未初始化造成死機

    最近在開發(fā)調(diào)試基于RT-Thread 的驅(qū)動時,遇到一個比較奇怪的死機問題,后來經(jīng)過一步步排查,終于發(fā)現(xiàn)是驅(qū)動的鏈表節(jié)點沒有初始化造成的死機
    的頭像 發(fā)表于 10-08 14:49 ?843次閱讀
    <b class='flag-5'>RT-Thread</b>使用經(jīng)驗分享:鏈表未<b class='flag-5'>初始化</b>造成死機