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 的作用如下表所示

初始化顺序API描述
1INIT_BOARD_EXPORT(fn)非常早期的初始化,此时调度器还未启动
2INIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多依赖的函数
3INIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4INIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统或者 LWIP
5INIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6NIT_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() 函数有两个参数,第一个参数表示需要初始化哪一个函数,传递的是函数指针也就是函数名,第二个参数表示将函数指针放到哪一个段。接下来就来分析一下这个宏

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

  • RT_USED
#define RT_USED                     __attribute__((used))

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

  • init_fn_t 类型
typedef int (*init_fn_t)(void);

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

  • ##

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

  • SECTION
#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.1INIT_BOARD_EXPORT(fn)
4.rti_fn.1.end__rti_init_rti_board_end
5.rti_fn.2INIT_PREV_EXPORT(fn)
6.rti_fn.3INIT_DEVICE_EXPORT(fn)
7.rti_fn.4INIT_COMPONENT_EXPORT(fn)
8.rti_fn.5INIT_ENV_EXPORT(fn)
9.rti_fn.6INIT_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() 函数以及 rt_componenets_init() 函数的执行位置。

使用示例

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

int led_init(void)
{
    return 1;
}
INIT_APP_EXPORT(led_init);

编译生成的 .map 文件如下图所示

在这里插入图片描述
函数指针 __rt_init_led_init 位于 .rti_fn.6 的段中,函数 rt_components_init() 函数执行的时候会对这个指针进行解引用,也就是执行 led_init 这个函数

参考:https://blog.csdn.net/yang1111111112/article/details/93982354?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值