RT-Thread 自动初始化机制的实现
RT-Thread 中的自动初始化机制使初始化函数不需要被显式调用, 只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。 自动初始化机制的使用在RT-Thread文档中已有介绍,本文主要结合 RT-Thread 源码分析其具体实现。
1. 自动初始化示例
int rt_hw_usart_init(void) /* 串口初始化函数 */
{
... ...
/* 注册串口 1 设备 */
rt_hw_serial_register(&serial1, "uart1",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */
在以上的示例代码中,串口初始化函数不需要被显式调用,只需要使用
宏INIT_BOARD_EXPORT进行申明,该函数的操作就会在板启动过程中
被执行。
2. 实现细节分析
以下对实现细节的分析基于使用GNU工具链构建移植到ARM Cortex-M3 架构上的RT-Thread的情况,使用其他工具链和平台时,原理基本上是一致的。
链接脚本中,在.text输出段描述中有如下一行:
KEEP (*(SORT(.rti_fn*)))
所有以.rti_fn开头的输入段按名称排序后放入.text输出段中。
接着,在rtdef.h中有如下的宏定义:
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn RT_SECTION(".rti_fn." level) = fn
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
其中,宏INIT_EXPORT的作用是定义一个前缀为__rt_init_
的函数指针,它指向函数fn并且处于前缀为.rti_fn.、后缀为level所表示的字符串
的段中。同理,INIT_BOARD_EXPORT定义了处于段.rti_fn.1 中的函数指针,指向fn。
在components.c中有如下的代码片段:
// ...
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");
// ...
void rt_components_board_init(void)
{
volatile 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)();
}
}
构建完成后,查看生成的.map文件,有如下内容:
.rti_fn.0.end 0x08002cb4 0x4 build/components.o
0x08002cb4 __rt_init_rti_board_start
.rti_fn.1 0x08002cb8 0x4 build/board.o
0x08002cb8 __rt_init_uart_init
.rti_fn.1.end 0x08002cbc 0x4 build/components.o
0x08002cbc __rt_init_rti_board_end
函数指针__rt_init_rti_board_start和__rt_init_rti_board_end是使用
INIT_EXPORT宏生成的,分别位于段.rti_fn.0.end和.rti_fn.1.end中,
段.rti_fn.1位于这两个段中间,所有使用宏INIT_BOARD_EXPORT生成的函数
指针均位于段.rti_fn.1中,它们的地址因此均位于函数指针__rt_init_rti_board_start
和__rt_init_rti_board_end的地址中间。
最后,通过函数rt_components_board_init中的for循环,使用函数指针的方式
调用了所有使用INIT_BOARD_EXPORT申明的函数。