轉載自 RT-Thread-RT-Thread 的 INIT_BOARD_EXPORT(fn) 宏 實現過程RT-Thread問答社區 - RT-Thread
[postbg]bg8.png[/postbg]由於項目需要,最近也開始接觸RTT,小白一枚。如有錯誤,多多指教。
今天在看RT-Thread啟動分析時,遇到了這樣一段代碼(下面紅色代碼):
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
}
首先 定義了結構體指針 desc 。跟蹤他的結構體定義 rt_init_desc 如下(下面紅色代碼):
#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);//成功返回0,失敗返回-1
#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; \
const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
{ __rti_##fn##_name, fn};
#else
#define INIT_EXPORT(fn, level) \
const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
#endif
#endif
#else
#define INIT_EXPORT(fn, level)
#endif
它里面 包括 一個 char 類型的指針 和一個 init_fn_t 類型 fn,繼續跟蹤 init_fn_t 定義 ,發現它為一個函數指針:typedef int (*init_fn_t)(void);
繼續回到 第一段代碼 ,分析for循環中的東西: for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
可以看到 desc 被&__rt_init_desc_rti_board_start 做了初始化。繼續跟蹤,程序調到了 這條宏 INIT_EXPORT(rti_board_start, "0.end");
繼續跟蹤,又到了第二段代碼的藍色部分。可以看出這個宏是兩個 比較牛逼的賦值語句。先分析第一個賦值語句:
const char __rti_##fn##_name[] = #fn;
定義了一個char 類型的__rti_##fn##_name數組。##fn是什么鬼?,其實他沒有想象中的那么牛逼,就是一個字符串的拼接。比如 fn為 "gw",那么__rti_##fn##_name就為__rti_gw_name。那#fn是什么意思了? 其實他就是給fn加個“”,讓他變成 “fn”,其中fn為具體值,比如fn為gw,那么他是“gw”。
第二個賦值 const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = { __rti_##fn##_name, fn};如何理解呢?先來分析SECTION(".rti_fn."level)。跟蹤SECTION(".rti_fn."level),發現它又是一個宏定義,如下:#define SECTION(x)
其中: __attribute__((section(x))) 是 INIT_BOARD_EXPORT(fn)的精華所在。
關於 它的詳細介紹,可以看這篇文章:link:__attribute__的section用法 。下面是我在他文章中截取的部分。
__attribute__的section子項的使用格式為:_attribute__((section("section_name")))其作用是將作用的函數或數據放入指定名為"section_name"輸入段。這里還要注意一下兩個概念:輸入段和輸出段輸入段和輸出段是相對於要生成最終的elf或binary時的Link過程說的,Link過程的輸入大都是由源代碼編繹生成的目標文件.o,那么這些.o 文件中包含的段相對link過程來說就是輸入段,而Link的輸出一般是可執行文件elf或庫等,這些輸出文件中也包含有段,這些輸出文件中的段就叫做輸 出段。輸入段和輸出段本來沒有什么必然的聯系,是互相獨立,只是在Link過程中,Link程序會根據一定的規則(這些規則其實來源於Link Script),將不同的輸入段重新組合到不同的輸出段中,即使是段的名字,輸入段和輸出段可以完全不同。其用法舉例如下:int var __attribute__((section(".xdata"))) = 0;這樣定義的變量var將被放入名為.xdata的輸入段,(注意:__attribute__這種用法中的括號好像很嚴格,這里的幾個括號好象一個也不能少。)
這一函數指針變量放入什么輸入段呢,請看__attribute__ ((__section__ (".initcall" levle ".init"))),輸入段的名稱由level決定,如果level="1",則輸入段是.initcall1.init,如果level="3s",則輸入段是.initcall3s.init。這一函數指針變量就是放在用這種方法決定的輸入段中的。
然后就簡單了。通過__attribute__((section(x))) 修飾后,代碼中所有的宏定義 INIT_EXPORT(x,y) 就會編譯到一個段里。
如何直觀的理解了?可以看自己工程編譯后的.map 文件。下面是我用429程序編譯后,生成的.map文件中的截圖:
這樣這段程序就可以讓人理解了。
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);
}
而我們在程序里看到的 INIT_BOARD_EXPORT(fn) ,如下面的截圖:
跟蹤一下,又是一個宏定義:#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")。正好定義成了咱們分析的這個宏。這樣我們就明白了INIT_BOARD_EXPORT(fn)到底是怎么被調用的了,真相終於大白了。下圖是調試串口的打印截圖: