一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头……
我们选择 MDK-ARM 集成开发环境作为目标硬件平台,通过单步执行的方式来观察来观察 RT-Thread 操作系统内核是如何运行启动的。在此之前我们先下载RTTheard官网已经移植好的STM32F103工程模板用来实验。下载链接keil模拟器STM32F103
打开project后我们按ctrl+F5进行软件仿真,系统复位后stm32会首先运行启动文件中的Reset_Handler函数。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Reset_Handler函数内部运行流程如下所示:
- 调用SystemInit函数进行stm32系统初始化;
- 跳转到main函数,即我们常说的系统入口函数。
有关Reset_Handler函数运行细节可参考[STM32启动文件初探之startup_stm32f10x_hd.s(Reset_Handler函数)](https://blog.csdn.net/qq_34706280/article/details/77823219)
我们单步执行到__main,
继续执行发现并没有进入main函数,而是跳转到了$Sub$$main
函数内部
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
在这里
$Sub$$main
函数仅仅调用了 rtthread_startup() 函数。 RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一入口点,所以 S u b Sub Sub$main 函数只需调用rtthread_startup() 函数即可(例如采用 GNU GCC 编译器编译的 RT-Thread,就是直接从汇编启动代码部分跳转到 rtthread_startup() 函数中,并开始第一个 C 代码的执行)。在 components.c 的代码中找到rtthread_startup() 函数,我们看到 RT-Thread 的启动流程如下图所示
其中 rtthread_startup() 函数的代码如下所示:
int rtthread_startup(void)
{
rt_hw_interrupt_disable(); //1
rt_hw_board_init(); //2
rt_show_version(); //3
rt_system_timer_init(); //4
rt_system_scheduler_init(); //5
rt_system_signal_init(); //6
rt_application_init(); //7
rt_system_timer_thread_init(); //8
rt_thread_idle_init(); //9
rt_system_scheduler_start(); //10
/* 不 会 执 行 至 此 */
return 0;
}
这部分启动代码,大致可以分为四个部分:
(1)初始化与系统相关的硬件;
(2)初始化系统内核对象,例如定时器、调度器、信号;
(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;
(4)初始化定时器线程、空闲线程,并启动调度器。
rt_hw_board_init() 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定
到这个串口,后续系统运行信息就会从串口打印出来。
main() 函数是 RT-Thread 的用户代码入口,用户可以在 main() 函数里添加自己的应用。