案例引入
一般初始化
嵌入式开发在初始化某个外设的时候大部分都是以下这种形式
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 的作用如下表所示
以上表格来自 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 宏导出的段分布如下表所示
加上自动初始化导出的 6 个段之后,各个段的分布如下表所示
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 这个函数。
原作者:tyustli