RT-Thread 的 INIT_BOARD_EXPORT(fn) 宏 实现过程
转载自 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)到底是怎么被调用的了,真相终于大白了。下图是调试串口的打印截图: