rt-thead启动流程
了解操作系统的启动流程能够更好的掌控我们的项目,根据需要二次开发。这里我们将循序渐进的梳理启动过程了解每个步骤都干了什么。
MCU启动流程,从Reset_Handler谈起
系统启动之前先有裸机的初始化过程
- startup.s
- Reset_Handler
- SystemInit
- __iar_program_start
- components.c
- __low_level_init
- __iar_data_init3
- rtthread_startup
- rt_hw_board_init
- rt_application_init
- rt_system_scheduler_start
rtthread系统自动初始化流程
RT-Thread 提供了一系列宏定义来支持不同阶段的自动初始化:
- rt_hw_board_init
- INIT_BOARD_EXPORT(fn): 用于硬件的初始化,此时调度器还未启动。
- main_thread_entry
- INIT_PREV_EXPORT(fn): 主要用于纯软件的初始化,没有太多依赖的函数。
- INIT_DEVICE_EXPORT(fn): 用于外设驱动初始化,例如网卡设备。
- INIT_COMPONENT_EXPORT(fn): 用于组件初始化,如文件系统或LWIP。
- INIT_ENV_EXPORT(fn): 用于系统环境初始化,例如挂载文件系统。
- INIT_APP_EXPORT(fn): 用于应用初始化,例如GUI应用。
实现原理
通过将指定数据划分到自定义的section
中实现编译时所有函数指针在flash中分组,再通过特殊手段拿到这个section的起始与结束,通过下文的循环遍历执行即可。
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
初看这些命名可能感觉比较魔幻不知所云,这里我们将拨开迷雾见明月,逐层分析了解这里的神奇设计是如何将分散在各个*.c
初始化程序入口统一放到一起实现了类似动态数组的效果。
rtdef.h中的宏定义
查看头文件rtdef.h
了解宏相关定义
查看 project.map
了解INIT_EXPORT(fn, level)
宏到底干了什么
//rtdef.h
#define INIT_EXPORT(fn, level) \
rt_used const init_fn_t __rt_init_##fn rt_section(".rti_fn." level) = fn
/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* init cpu, memory, interrupt-controller, bus... */
#define INIT_CORE_EXPORT(fn) INIT_EXPORT(fn, "1.0")
/* init pci/pcie, usb platform driver... */
#define INIT_FRAMEWORK_EXPORT(fn) INIT_EXPORT(fn, "1.1")
/* init platform, user code... */
#define INIT_PLATFORM_EXPORT(fn) INIT_EXPORT(fn, "1.2")
/* init sys-timer, clk, pinctrl... */
#define INIT_SUBSYS_EARLY_EXPORT(fn) INIT_EXPORT(fn, "1.3.0")
#define INIT_SUBSYS_EXPORT(fn) INIT_EXPORT(fn, "1.3.1")
/* init early drivers */
#define INIT_DRIVER_EARLY_EXPORT(fn) INIT_EXPORT(fn, "1.4")
/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initialization) */
#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")
/* application initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
/* init after mount fs */
#define INIT_FS_EXPORT(fn) INIT_EXPORT(fn, "6.0")
/* init in secondary_cpu_c_start */
#define INIT_SECONDARY_CPU_EXPORT(fn) INIT_EXPORT(fn, "7")
//rtcompiler.h
#define rt_section(x) @ x //IAR 环境
#define rt_section(x) __attribute__((section(x))) #MDK环境
除了初始化过程,FinSH命令交互也使用了同样的技术
### section是什么?
这里需要补充编译器的链接相关知识
从存区功能划分
-
只读ROM
- 非易失性存储器,掉电不丢失数据
- flash eeprom
-
读写 RAM
- 易失性存储器,掉电不丢失数据
- DRAM SDRAM PRAM 等
从程序中划分
种类 | 描述 | |
---|---|---|
code | 机器代码指令 | |
RO-data | 常量数据 只读数据 | |
RW-data | 初值非0 的全局变量 | |
ZI-data | 初值为0 的全局变量 |
在编译过程中,为方便管理又对不同的编辑结果进行分组
Section | Kind | 描述 |
---|---|---|
.intvec | ro code | 程序向量表 |
.text | ro code | 代码指令默认**.text** |
.rodata | const | 常量只读数据 RO-data |
.data | inited | 初值非0 的全局变量 |
.bss | zero | 初值为0 的全局变量 |
CSTACK | uninit | 不初始化的全局变量 |
FSymTab | const | rtthread自定义的FinSH命令用到的 |
.rti_fn.0 | const | rtthread自定义自动初始化0组地址 |
.rti_fn.0.end | const | rtthread自定义自动初始化0组结束地址 |
再看__rt_init_rti_board_start
与__rt_init_rti_board_end
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start, "0.end");
//展开 可以看到放在了自定义的.rti_fn.0.end
rt_used const init_fn_t __rt_init_rti_board_start rt_section(".rti_fn." level) = rti_board_start
static int rti_board_end(void)
{
return 0;
}
INIT_EXPORT(rti_board_end, "1.end");
//展开 可以看到放在了自定义的.rti_fn.1.end
rt_used const init_fn_t __rt_init_rti_board_end rt_section(".rti_fn." level) = rti_board_end
至此我们已经完全梳理出rtthread系统的自动初始化流程具体的工作细节了。
- 新定一个函数指针__rt_init_XXX,并将指针放到自定的section中
- 根据section根据命名自动排序规则创建一个特殊的函数
rti_board_end,rti_end
来划分每个section的结束地址 - 再在合适的位置循环遍历指定区间的函数即可完成自动初始化工作。