RTT筆記-分析自動初始化機制轉
首先全域性搜尋一個任意的自啟動巨集,便能找到在rtdef.h中由如下定義
1 #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") 2 3 /* pre/device/component/env/app init routines will be called in init_thread */ 4 /* components pre-initialization (pure software initilization) */ 5 #define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") 6/* device initialization */ 7 #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") 8 /* components initialization (dfs, lwip, ...) */ 9 #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") 10 /* environment initialization (mount disk, ...) */ 11 #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") 12/* appliation initialization (rtgui application etc ...) */ 13 #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
關於巨集INIT_EXPORT的定義就就在上方
1 #ifdef RT_USING_COMPONENTS_INIT 2 typedef int (*init_fn_t)(void); 3 #ifdef _MSC_VER /* we do not support MS VC++ compiler */ 4 #define INIT_EXPORT(fn, level) 5針對上面程式碼,逐句分析下。#else 6 #if RT_DEBUG_INIT 7 struct rt_init_desc 8 { 9 const char* fn_name; 10 const init_fn_t fn; 11 }; 12 #define INIT_EXPORT(fn, level) \ 13 const char __rti_##fn##_name[] = #fn; \ 14 RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \ 15 { __rti_##fn##_name, fn}; 16 #else 17 #define INIT_EXPORT(fn, level) \ 18 RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn 19 #endif 20 #endif 21 #else 22 #define INIT_EXPORT(fn, level) 23 #endif
首先RT_USING_COMPONENTS_INIT巨集需要在config.h中定義,否則自啟動是無效的。
然後使用typedef定義了一個函式指標型別
這裡補充一下關於typedef:
目前我知道的typedef有兩種用法,其一是起別名,其二是定義新的型別,舉個例程說明這兩種用法
1 //生產了新型別fun_p 2 typedef int (*fun_p)(void); 3 int app(void) 4 { 5 return 0; 6 } 7 8 typedef struct sTest 9 { 10 fun_p * app_p; 11 }Test_s; 12 13 Test_s test; 14 tset.app_p = app;
回到上文,由於#ifdef後的巨集均是未定義,所以一路走else,那麼就僅僅剩一句話
1 RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
首先看看RT_USED這個巨集,通用定義也在rtdeh.h中 1 #define RT_USED __attribute__((used))
- attribute((used))識別符號作用是使定義被標記的函式或資料即使未使用也不會被編譯器優化。
- init_fn_t是一個函式指標型別
- __rt_init_##fn是將__rt_init_和我們傳入的需要自啟動的函式名進行拼接
- SECTION(".rti_fn."level)也就是 __attribute__((section( ".rti_fn."level ))),
該函式便是實現自動初始化的關鍵了,他的作用是將標記的資料或者函式在編譯時放到name的資料段中去。
例如系統中有如下語句 1 components.c(60) : INIT_EXPORT(rti_start, "0"); 在編譯後生成的map檔案中能夠找到對應資訊,名叫__rt_init_rti_start 的指標被儲存在了.rti_fn.0欄位中去 __rt_init_rti_start 0x0801e6b8 Data 4 components.o(.rti_fn.0)
綜上那麼完整語句的翻譯便是: 定義了一個名為(_rt_init+需要自動啟的函式名)的函式指標,將其儲存在(.rti_fn.level)資料段中,並且及時不使用也不會被編譯器優化。
到這裡基本就能明白自啟動的方式了。也就是逐個建立一個指標指向需要自啟動的函式,然後將這些指標儲存到特定的資料段中。main啟動時候,只需要將資料段中的指標函式全部執行一遍即可。
接下來我們看執行初始化的地方,也就是在components.c中
一上來便定義了一些標杆,用來區間化之前準備往裡塞的函式指標
1 static int rti_start(void) 2 { 3 return 0; 4 } 5 INIT_EXPORT(rti_start, "0"); 6 7 static int rti_board_start(void) 8 { 9 return 0; 10 } 11 INIT_EXPORT(rti_board_start, "0.end"); 12 13 static int rti_board_end(void) 14 { 15 return 0; 16 } 17 INIT_EXPORT(rti_board_end, "1.end"); 18 19 static int rti_end(void) 20 { 21 return 0; 22 } 23 INIT_EXPORT(rti_end, "6.end");
我們再看看map中的情況
1 __rt_init_rti_start 0x0801e6dc Data 4 components.o(.rti_fn.0) 2 __rt_init_rti_board_start 0x0801e6e0 Data 4 components.o(.rti_fn.0.end) 3 __rt_init_rt_hw_spi_init 0x0801e6e4 Data 4 drv_spi.o(.rti_fn.1) 4 __rt_init_rti_board_end 0x0801e6e8 Data 4 components.o(.rti_fn.1.end) 5 __rt_init_ulog_init 0x0801e6ec Data 4 ulog.o(.rti_fn.2) 6 __rt_init_ulog_console_backend_init 0x0801e6f0 Data 4 console_be.o(.rti_fn.2) 7 __rt_init_finsh_system_init 0x0801e6f4 Data 4 shell.o(.rti_fn.6) 8 __rt_init_fal_init 0x0801e6f8 Data 4 fal.o(.rti_fn.6) 9 __rt_init_rti_end 0x0801e6fc Data 4 components.o(.rti_fn.6.end) 10
我們想自啟動的函式__rt_init_rt_hw_spi_init、__rt_init_ulog_init 等都被包夾在了這些標識杆中間。至於他的排序問題,文末將用程式碼進行測試推論。
按照系統執行順序來分別看下自啟動的兩個函式:
首先是 rtthread_startup() →void rt_hw_board_init() →void rt_components_board_init(void)
首先是 rtthread_startup() →void rt_hw_board_init() →void rt_components_board_init(void)
1 const init_fn_t *fn_ptr; 2 3 for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) 4 { 5 (*fn_ptr)(); 6 }
其中__rt_init_rti_board_start和__rt_init_rti_board_end便是上面的兩個標誌杆,是經過巨集裡面的##拼接後的結果,然後我們再看看上面的map,就發現這個for迴圈實際上是執行了被包夾的__rt_init_rti_board_start和__rt_init_rt_hw_spi_init,拆解一下就是函式rti_board_start和rt_hw_spi_init。
我們再看第二個自啟動的函式
rtthread_startup() →rt_application_init()→void main_thread_entry(void *parameter)→rt_components_init();
1 const init_fn_t *fn_ptr; 2 3 for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++) 4 { 5 (*fn_ptr)(); 6 }
這裡和上面類似,只是標誌杆變為了level2~6之間的函數了。也就是level02間的函式是一起執行的,level2~6間的函式是一起執行的。
下來我們研究一下這個欄位的排序問題首先由已知,在.rti_fn.後面是以數由小到大排序。
那麼嘗試一下在後面新增字元,新增兩個新的標誌杆
字元排在了數字後面,然後再新增一個大寫字母
A排序到了小寫字母之前數字之後,也就是這個排序可能就是ascii碼的排序了。
還有個問題就是同欄位的兩個函式指標的順序如何呢,例如
1 __rt_init_ulog_init 0x0801e6f8 Data 4 ulog.o(.rti_fn.2) 2 __rt_init_ulog_console_backend_init 0x0801e6fc Data 4 console_be.o(.rti_fn.2)
我將之前的標杆修改為
1 //測試用標誌杆 2 static int rti_A(void) 3 { 4 return 0; 5 } 6 INIT_EXPORT(rti_A, "2"); 7 8 //測試用標誌杆 9 static int rti_a(void) 10 { 11 return 0; 12 } 13 INIT_EXPORT(rti_a, "2"); 14 15 static int rti_1(void) 16 { 17 return 0; 18 } 19 INIT_EXPORT(rti_1, "2");
然後map結果是:
1 __rt_init_rti_start 0x0801e6e8 Data 4 components.o(.rti_fn.0) 2 __rt_init_rti_board_start 0x0801e6ec Data 4 components.o(.rti_fn.0.end) 3 __rt_init_rt_hw_spi_init 0x0801e6f0 Data 4 drv_spi.o(.rti_fn.1) 4 __rt_init_rti_board_end 0x0801e6f4 Data 4 components.o(.rti_fn.1.end) 5 __rt_init_rti_A 0x0801e6f8 Data 4 components.o(.rti_fn.2) 6 __rt_init_rti_a 0x0801e6fc Data 4 components.o(.rti_fn.2) 7 __rt_init_rti_1 0x0801e700 Data 4 components.o(.rti_fn.2) 8 __rt_init_ulog_init 0x0801e704 Data 4 ulog.o(.rti_fn.2) 9 __rt_init_ulog_console_backend_init 0x0801e708 Data 4 console_be.o(.rti_fn.2) 10 __rt_init_finsh_system_init 0x0801e70c Data 4 shell.o(.rti_fn.6) 11 __rt_init_fal_init 0x0801e710 Data 4 fal.o(.rti_fn.6) 12 __rt_init_rti_end 0x0801e714 Data 4 components.o(.rti_fn.6.end) 13
可以看出排序和我程式碼順序有關,也就是應該和編譯順序有關。