自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。
在RTT的代码中,有rt_components_init()
和rt_components_board_init();
两个函数,用于实现自动初始化机制。
RTT官方文档的系统启动流程图中,6种注册分别于上述两个函数中实现自动初始化。
两个初始化函数也很简单,就是调用从__rt_init_rti_board_start
到__rt_init_rti_board_end
的函数执行一遍;__rt_init_rti_board_end
到__rt_init_rti_end
的函数执行一遍。
/**
* RT-Thread Components Initialization for board
*/
void rt_components_board_init(void)
{
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)();
}
}
/**
* RT-Thread Components Initialization
*/
void rt_components_init(void)
{
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
(*fn_ptr)();
}
}
由typedef int (*init_fn_t)(void);
可知,fn_ptr 是一个32bit的指针,刚好stm32是32bit寻址的,可以存储需要初始化的函数。
官方给的注册接口也很简单:
初始化顺序 | 宏接口 | 描述 |
---|---|---|
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 应用 |
我们只需要根据自己的需求,进行相应的函数注册,就可以实现自动初始化了,但具体这个注册过程是怎么实现的呢?可以查看这些INIT的定义。
不同的INIT对应了同一个函数:INIT_EXPORT
,这个函数有两个参数,第一个是传入的函数指针,第二个是一个数字。
/* 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")
我们可以简单理解为第二个数字代表的是初始化顺序,可INIT_EXPORT
具体是怎么实现的呢?来看下它的定义:
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
emm……去查下这些宏的定义:
#define RT_USED __attribute__((used))
typedef int (*init_fn_t)(void);
#define SECTION(x) __attribute__((section(x)))
由GCC手册可知: __attribute__((used))
表示这个标记这个东西是使用过的,避免出现如: warning: #177-D: variable "a" was declared but never referenced
的警告。
__attribute_unused__和__attribute_used__的作用
在GCC的宏中,##后面跟变量名。
__attribute__((section(x)))
则表示fn被放置于指定段中。
可以找到在RTT的初始化过程中,有如下各种INIT:
/*
* Components Initialization will initialize some driver and components as following
* order:
* rti_start --> 0
* BOARD_EXPORT --> 1
* rti_board_end --> 1.end
*
* DEVICE_EXPORT --> 2
* COMPONENT_EXPORT --> 3
* FS_EXPORT --> 4
* ENV_EXPORT --> 5
* APP_EXPORT --> 6
*
* rti_end --> 6.end
*
* These automatically initialization, the driver or component initial function must
* be defined with:
* INIT_BOARD_EXPORT(fn);
* INIT_DEVICE_EXPORT(fn);
* ...
* INIT_APP_EXPORT(fn);
* etc.
*/
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");
对照着映像文件可以看到:
选中部分,第一行是一个Section,叫做.rti_fn.0
,这个内容实际是我们通过INIT_EXPORT(rti_start, "0");
完成的,我们把函数rti_start
改名为__rt_init_rti_start
,存入.rti_fn.0
这个地方。
同样的,INIT_EXPORT(rti_board_start, "0.end");
、INIT_EXPORT(rti_board_end, "1.end");
、INIT_EXPORT(rti_end, "6.end");
也是这里插入的。
注意在这个映像文件中的第2728行,多了一个内容,在shell.c中找到一行:
INIT_APP_EXPORT(finsh_system_init);
通过上面我们知道,通过APP_EXPORT导入的会存放在名为6的Section中,所以这里应该是存放的finsh_system_init
的函数指针。
通过Debug查看0x080088e4中的内容,其值为0x08003D39。
监视函数finsh_system_init
,其值正好是0x08003D39。
因为其是Thumb指令,所以监视到的最后一个bit为1,正是我们的函数地址。
最后我们再回到这个初始化的过程(以第一个流程为例):
/**
* RT-Thread Components Initialization for board
*/
void rt_components_board_init(void)
{
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)();
}
}
对照着映像表,从符号名为__rt_init_rti_board_start
(注册名为rti_board_start
)(段名为0.end
)开始的地方,取出其存放的32bit的值作为函数地址,执行这些函数,一直到符号名为__rt_init_rti_board_end
(注册名为rti_board_end
)(段名为1.end
)的地方结束。