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申明的函数。

3. 参考