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()
函数有两个参数,第一个参数表示需要初始化哪一个函数,传递的是函数指针也就是函数名,第二个参数表示将函数指针放到哪一个段。接下来就来分析一下这个宏
分析之前需要有几个预备知识
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.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