- 總結RT-Thread的啟動流程。
- 非運行時與運行時的image文件分別是什么樣的,請畫下來。是誰將 RW 段中的 RW-data(初始化的全局變量)搬運到 RAM 中?
- MDK環境下各種數據段存儲的什么數據?
- 在RT-Thread啟動時,關了中斷,那么在什么時候開啟的中斷?
- 總結自動初始化原理。
- 總結BSP制作過程。
1、RT-Thread啟動流程
這部分啟動代碼,大致可以分為四個部分:
(1) 初始化與系統相關的硬件;
(2) 初始化系統內核對象,例如定時器、調度器、信號;
(3) 創建 main 線程,在 main 線程中對各類模塊依次進行初始化;
(4) 初始化定時器線程、空閑線程,並啟動調度器。
啟動流程中藍色部分是自動初始化的數據段,使用自動初始化宏導出的函數放置到相應的數據段,在啟動流程中對函 數進行遍歷初始化。2、加載時地址與運行時地址映射
image文件

STM32 在上電啟動之后默認從 Flash 啟動,啟動之后會將 RW 段中的 RW-data(初始化的全局變量)搬運到 RAM 中,但不會搬運 RO 段,即 CPU 的執行代碼從 Flash 中讀取,另外根據編譯器給出的 ZI 地址和大小分配出 ZI 段,並將這塊 RAM 區域清零。
分散裝載配置文件里會有配置,關於code的地址,有兩個設置,一個是存儲地址(這個地址配置的是燒寫器把代碼 段寫到flashrom的何處),一個是裝載運行地址,也就是你程序在什么地方運行
【如果想要深入了解的話,可以看看arm的連接器手冊,或者是《ARM體系結構與編程》中也講到了】
3、MDK環境下各種數據段存儲的什么數據?
code:代碼段,存放程序
RO:只讀數據段,存放程序中定義的常量RW:讀寫數據段,存放非0全局變量
ZI:0數據段,存放未初始化的全局變量與初始化為0的變量
MDK 在編譯完成之后
Total RO Size (Code + RO Data) 53668 ( 52.41kB) Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
1) RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空間的大小;
2) RW Size 包含了 RW-data 及 ZI-data,表示運行時占用的 RAM 的大小;
3) ROM Size 包含了 Code、RO Data 以及 RW Data,表示燒寫程序所占用的 Flash 空間的大小;
4、在RT-Thread啟動時,關了中斷,那么在什么時候開啟的中斷?
在啟動調度器,切換到第一個線程時開啟的中斷【直接使用CPSIE I 開了中斷的】。代碼詳見:
rt_system_scheduler_start() rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
5、總結自動初始化原理。
RT-Thread 的自動初始化機制使用了自定義 RTI 符號段,將需要在啟動時進行初始化的函數指針放到了該段中,形成一張初始化函數表,在系統啟動過程中會遍歷該表,並調用表中的函數,達到自動初始化的目的。
用來實現自動初始化功能的宏接口定義詳細描述如下表所示:
初始化順序 |
宏接口 |
描述 |
1 |
INIT_BOARD_EXPORT(fn) |
非常早期的初始化,此時調度器還未啟動 |
2 |
INIT_PREV_EXPORT(fn) |
主要是用於純軟件的初始化、沒有太多依賴的函數 |
3 |
INIT_DEVICE_EXPORT(fn) |
外設驅動初始化相關,比如網卡設備 |
4 |
INIT_COMPONENT_EXPORT(fn) |
組件初始化,比如文件系統或者 LWIP |
5 |
INIT_ENV_EXPORT(fn) |
系統環境初始化,比如掛載文件系統 |
6 |
INIT_APP_EXPORT(fn) |
應用初始化,比如 GUI 應用 |
初始化函數主動通過這些宏接口進行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),鏈接器會自動收集所有被申明的初始化函數,放到 RTI 符號段中,該符號段位於內存分布的 RO 段中,該 RTI 符號段中的所有函數在系統初始化時會被自動調用。
原理:
在rtdef.h中,使用SECTION(x)定義:
#define SECTION(x) attribute ((section(x)))
attribute ((section("name"))):將作用的函數或數據放入指定名為"name"的輸入段中。(在不同的編譯器中實現的方式也有所不同。)
將SECTION(".rti_fn."level)使用 INIT_EXPORT(fn, level) 這個宏進行定義,fn是函數
#define INIT_EXPORT(fn, level) RT_USED const init_fn_t rt_init_##fn SECTION(".rti_fn."level) = fn
分段:
compnents.c中:
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");
所以就有:
段名 |
函數指針/宏 |
.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 |
非調試模式下rt_components_board_init():for循環會遍歷位於 rt_init_rti_board_start 到
rt_init_rti_board_end 之間保存的函數指針,然后依次執行這些函數
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_init():for循環會遍歷位於 rt_init_rti_board_end 到 rt_init_rti_end 之間保存的函數指針,然后依次執行這些函數
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 }
舉例:
main函數中添加了函數pin_beep_sample(),並使用INIT_APP_EXPORT()進行自動初始化。
INIT_APP_EXPORT(pin_beep_sample);
那么,展開為:
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) 。)
在編譯后的.map文件中可以查看到:
Symbol Name Value Ov Type Size Object(Section)