其他分享
首页 > 其他分享> > RT-Thread 自动初始化机制详解

RT-Thread 自动初始化机制详解

作者:互联网

RT-Thread 自动初始化机制详解

介绍

一般初始化

嵌入式开发在初始化某个外设的时候大部分都是以下这种形式

int main(int argc, char *argv[])
{
    clk_init();
    led_init();
    beep_init();
    key_init();
    .....
    while(1)
    {
        ...
    }
}

上面的初始化顺序比较清晰,初始化了哪些外设以及先后顺序一眼就可以看出来,但是这样 main 函数显得特别繁琐,尤其是需要初始化的外设比较多的时候。

自动初始化

在电脑编写 C 语言程序的时候,打印一个 hello world

#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("hello world\r\n");
    return 1;
}

这里我们直接就可以使用 printf 进行打印,而没有进行一些其它的初始化,参考这个思路引出了 RT-Thread 的自动初始化机制。

RT-Thread 自动初始化介绍

int led_init()
{
    ...
}
INIT_APP_EXPORT(led_init);

int main(int argc, char *argv[])
{
	led_on();
    rt_kprintf("hello rt thread\r\n");
    return 1;
}

自动初始化的核心思想就是在执行到 main 函数之前,各个外设的初始化全部都初始化完成了,直接在 main 函数中使用即可。例如上面的程序中直接使用 rt_kprintf 进行输出,以及 LED 的点亮。

自动初始化 API

RT-Thread 源码中截取的自动初始化的 API 如下

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")
初始化顺序 API 描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动
2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数
3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备
4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP
5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统
6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用

​ 以上表格来自 RT-Thread 官网

原理分析

INIT_EXPORT 函数

从各个初始化函数可以看出最终调用的都是 INIT_EXPORT 这一个函数,只是传递的参数不同。接下来看一下这个函数的定义

#define INIT_EXPORT(fn, level)                                                       \
    RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

INIT_EXPORT() 函数有两个参数,第一个参数表示需要初始化哪一个函数,传递的是函数指针也就是函数名,第二个参数表示将函数指针放到哪一个段。接下来就来分析一下这个宏

分析之前需要有几个预备知识

#define RT_USED                     __attribute__((used))

标记为 attribute__((used)) 的函数被标记在目标文件中,以避免链接器删除未使用的节。

typedef int (*init_fn_t)(void);

这里定义了一个返回值为 int,函数参数为 void 的一个函数指针类型并重命名为 init_fn_t

## 这个属于 C 语言的知识,它的作用是用来把两个语言符号组合成单个语言符号

#define SECTION(x)                  __attribute__((section(x)))

__attribute__((section(name))) 将作用的函数或数据放入指定名为 name 的输入段中

有了上面的预备知识后再来分析以下 INIT_EXPORT 这个宏,将宏展开后如下所示

RT_USED const init_fn_t __rt_init_fn SECTION(".rti_fn." level) = fn

这个宏的作用就是将函数 fn 的指针赋值给 __rt_init_fn 这个变量,这个变量的类型是 RT_USED const init_fn_t,这个变量存放在指定的段 .rti_fn.level 中。所以函数使用自动初始化宏导出后,这些数据段中就会存储指向各个初始化函数的指针。当我们对这些指针进行解引用的时候也就相当于执行了相应的函数。

段的划分

component.c 中对各个段进行了划分,源码如下

static int rti_start(void)
{
    return 0;
}
INIT_EXPORT(rti_start, "0");

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");

static int rti_end(void)
{
    return 0;
}
INIT_EXPORT(rti_end, "6.end");

上面使用 INIT_EXPORT 宏导出的段分布如下表所示

序号 段名 函数指针/函数名
1 .rti_fn.0 __rt_init_rti_start
2 .rti_fn.0.end __rti_init_rti_board_start
3 .rti_fn.1.end __rti_init_rti_board_end
4 .rti_fn.6.end __rti_init_rti_end

加上自动初始化导出的 6 个段之后,各个段的分布如下表所示

序号 段名 函数指针/函数名
1 .rti_fn.0 __rt_init_rti_start
2 .rti_fn.0.end __rti_init_rti_board_start
3 .rti_fn.1 INIT_BOARD_EXPORT(fn)
4 .rti_fn.1.end __rti_init_rti_board_end
5 .rti_fn.2 INIT_PREV_EXPORT(fn)
6 .rti_fn.3 INIT_DEVICE_EXPORT(fn)
7 .rti_fn.4 INIT_COMPONENT_EXPORT(fn)
8 .rti_fn.5 INIT_ENV_EXPORT(fn)
9 .rti_fn.6 INIT_APP_EXPORT(fn)
10 .rti_fn.6.end __rti_init_rti_end

rt_components_board_init 函数

有了上面段的划分,接下来看一下 rt_components_board_init 函数的实现

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
    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)();
    }
#endif
}

不考虑 RT_DEBUG_INIT 那么此函数最终的执行的就是

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)();
}

这段代码定义了一个 fn_ptr 指针,当指针的范围在 __rt_init_rti_board_start __rt_init_rti_board_end 范围之内时就对该指针进行解引用,这里的指针就是自动初始化时放的函数指针,所以这里就相当与函数的执行。也就是执行了 INIT_BOARD_EXPORT(fn) 导出的函数

rt_components_init 函数

该函数的源码如下

void rt_components_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;

    rt_kprintf("do components initialization.\n");
    for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
    {
        rt_kprintf("initialize %s", desc->fn_name);
        result = desc->fn();
        rt_kprintf(":%d done\n", result);
    }
#else
    volatile const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
#endif
}

同样不考虑 RT_DEBUG_INIT ,该函数最终执行的是

volatile const init_fn_t *fn_ptr;

for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
    (*fn_ptr)();
}

这段代码同样也定义了一个 fn_ptr 指针,当指针的范围在 __rt_init_rti_board_end __rt_init_rti_end范围之内时就对该指针进行解引用,这里的指针就是自动初始化时放的函数指针,所以这里就相当与函数的执行。也就是执行了INIT_PREV_EXPORT(fn)INIT_APP_EXPORT(fn) 这两个段之间导出的函数

自动初始化函数的执行

RT-Thread 的启动流程如下图所示

上图来自 RT-Thread 官网

rt_components_board_init()完成了第 1 段, rt_components_init()完成了第2 到第6 段。

rt_components_board_init() 完成了第 1 段,也就是初始化了由INIT_BOARD_EXPORT(fn) 的初始化的所有函数,也就是__rt_init_rti_board_start__rt_init_rti_board_end 之间的函数指针。
rt_components_init() 完成了第2 到第6 段,也就是按顺序初始化了由INIT_PREV_EXPORT(fn)INIT_DEVICE_EXPORT(fn)INIT_COMPONENT_EXPORT(fn)INIT_ENV_EXPORT(fn) INIT_APP_EXPORT(fn)初始化的所有函数,也就是从 __rt_init_rti_board_end__rt_init_rti_end 之间的函数指针。
所以,当你使用自动初始化导出宏 去初始化一个函数时,是由系统中的这两个函数进行遍历函数指针执行的。

从系统的启动流程可以看出,rt_components_board_init() 函数以及 rt_componenets_init() 函数的执行位置。

使用示例

board.c 函数中添加如下测试代码

static int rt_hw_spi_flash_init(void)
{
    __HAL_RCC_GPIOF_CLK_ENABLE();
    rt_hw_spi_device_attach("spi5", "spi5a", GPIOF, GPIO_PIN_6);

    if (RT_NULL == rt_sfud_flash_probe("W25Q256", "spi5a"))
    {
        return -RT_ERROR;
    };
    rt_hw_spi_device_attach("spi2", "spi2a", GPIOG, GPIO_PIN_10);
    return RT_EOK;
}
/* 导出到自动初始化 */
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);

放大后如下

函数指针 __rt_init_rt_hw_spi_flash_init 位于 .rti_fn.4 的段中,函数 rt_components_init() 函数执行的时候会对这个指针进行解引用,也就是执行 rt_hw_spi_flash_init 这个函数

至此,介绍完毕。

标签:RT,初始化,Thread,rti,INIT,init,EXPORT,rt,fn
来源: https://www.cnblogs.com/xqyjlj/p/13111024.html