本文挖了挖自动初始化的实现,限于水平挖不到底,
自动初始化很好用,不需要我们再去包含头文件调用函数,只要一句话cpu就会在特定的地方调用。自动初始化是6个宏。自动初始化的使用,官方的文档中心里是已经说的很明白了
初始化顺序 | 宏接口 | 描述 |
---|---|---|
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 应用 |
我们就来捋一遍为什么一个宏就能让代码自动执行
这里牵涉到RTT的启动流程。
首先大家需要知道不同编译器对应的代码入口不同,
编译器 | 入口函数 |
---|---|
__CC_ARM (Keil AC5) | int $Sub$$main(void) |
__CLANG_ARM (Keil AC6) | int $Sub$$main(void) |
__ICCARM__(IAR) | int __low_level_init(void) |
_GNUC_ (RTTstudio GCC) | int entry(void) |
RTThread将这些函数全部实现为调用rtthread_startup函数。所以,rtthread_startup函数是所有RTT工程的入口。rtthread_startup函数内容如下(有删减)
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initialization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
可以看到,直到最后,RTThread才开启调度器,在此之前系统一直是未运行的状态。
依次找到rt_hw_board_init() -> rt_components_board_init() 内容如下
可以看到,在这里使用函数指针执行了从__rt_init_rti_board_start在内存中的位置A到__rt_init_rti_board_end在内存中的位置B,A到B中的所有函数。我们可以猜测到RTT自动初始化宏的作用就是将函数指针放到AB之间连续的位置,我们可以尝试操作一下,然后翻一翻map文件看看,编写以下代码,放到main.c末尾
int fun_BOARD(void){return RT_EOK;}
int fun_PREV(void){return RT_EOK;}
int fun_DEVICE(void){return RT_EOK;}
int fun_COMPONENT(void){return RT_EOK;}
int fun_ENV(void){return RT_EOK;}
int fun_APP(void){return RT_EOK;}
INIT_BOARD_EXPORT(fun_BOARD);
INIT_PREV_EXPORT(fun_PREV);
INIT_DEVICE_EXPORT(fun_DEVICE);
INIT_COMPONENT_EXPORT(fun_COMPONENT);
INIT_ENV_EXPORT(fun_ENV);
INIT_APP_EXPORT(fun_APP);
之后在工程目录中搜索后缀为 .map 的文件,打开后可以找到下图中的内容
我们可以发现,我们写的6个测试函数都被放到了连续的内存之中,验证了我们之前的猜测。那么为什么可以这样呢?我们对任意一个宏go to definition
可以看到他们调用的都是INIT_EXPORT宏,对它go to definition可以看到
看到这里不用慌,首先前面的RT_USED和const我们不用看,init_fn_t是一个函数指针类型
go to definition看到
是一个返回值为int,形参为void的函数指针类型。所以可以看出INIT_EXPORT其实就是在定义函数指针,这个函数指针的类型是 init_fn_t,名称是 __ rt_init_##fn,就是__rt_init_+fn名称。比如我们写INIT_BOARD_EXPORT(fun_BOARD);,函数指针就是__rt_init_fun_BOARD,从.map文件里也可以看到确实是这样。而这个函数指针本身的位置,靠SECTION(".rti_fn." level)指定。SECTION定义如下
__attribute__就是指定内存位置用的,而其参数section(x)已经不能再go to definition了。
就到此为止吧。函数指针指向的内容就是fn,也就是我们传入的函数。
总结一下
1,自动初始化里的函数必须符合init_fn_t指针形式,否则会报警告。比如
int fun(void)
{
/*do something*/
return RT_EOK;
}
2,自动初始化所有的宏都是在一处初始化,执行顺序见表,都是在系统运行前执行。
链接: _Doon。 stm32 函数段使用