目录
RTT(RT-Thread)内核启动流程详解
前言
与STM32裸机启动流程一样,RT-Thread启动流程分为汇编阶段和C阶段。首先在汇编代码里面去创建好C语言执行的环境,然后调用系统初始化函数把我们的系统进行初始化(其中就包括了系统时钟的初始化),初始化完成以后就进入了C语言的main函数入口。不过RTT在进入main函数之前还做了对系统内核一些功能的初始化。
RTT内核启动流程
RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示:(RT-Thread Studio使用的是GCC编译器)
启动流程概述
启动的第一阶段从启动汇编代码开始执行,我们的编译环境一共有三种
分别为MDK(如Keil5)、IAR、GCC(如VScode、CLion等),不同开发工具调用的函数都是不一样的,不过它们最终的结果都是进入到rtthread_startup函数里执行。
我们使用的编译软件是RT Thread Stduio,它使用的是GCC编译环境,它执行完启动文件,然后经过入口函数entry()以后,最终调用了rtthread_startup函数
在rtthread_startup函数里首先,执行rt_hw_interrupt_disable,关闭所有的硬件中断;
然后分别执行
rt_hw_board_init:初始化板子相关的硬件外设
rt_show_version:调用显示版本函数
rt_system_timer_init:系统定时器初始化
rt_system_scheduler_init:系统线程调度器初始化
rt_system_signal_init:系统信号初始化
rt_application_init:应用层初始化
rt_system_timer_thread_init:定时器线程初始化
rt_thread_idle_init:空闲线程的初始化
最后执行rt_system_scheduler_start,启动系统线程调度器,执行相关线程
在应用层初始化函数、系统定时器线程初始化函数、空闲线程函数中分别创建了三个线程,然后当我们的线程调度器工作以后就会调度这三个线程去执行(其中idle线程的优先级最低)
在我们的主线程入口,会对组件进行初始化,同时最终会跳转到用户定义的main函数中
汇编阶段
Reset_Handler: //复位
/* Copy the data segment initializersfrom flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock systemintitialization function.*/
bl SystemInit
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entrypoint.*/
bl entry
bx lr
主要过程:
- 从Flash中拷贝数据段到SRAM中
- 清空BSS段(BSS段清零),创建好C语言的开发环境,由于C语言的全局未初始化的变量是放在BSS段的,因此打印未初始化的全局变量,值是0
- 初始化系统时钟(SystemInit)
我们首先在board.h中设置好时钟
最终调用SystemInit进行初始化
- 进入entry入口
C阶段
【1】系统时钟初始化
system_stm32f1xx.c中的系统初始化函数,参考之前章节内容
时钟系统配置文件board.h
使用外部高速时钟,时钟源晶振8MHz,系统时钟72MHz
1、entry入口
int entry(void)
{
rtthread_startup();
return 0;
}
2、进入rtthread_startup函数
int rtthread_startup(void)
{
rt_hw_interrupt_disable(); //关闭硬件中断
/*
* board level 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();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
#ifdef RT_USING_SMP
rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/
/* start scheduler */
rt_system_scheduler_start();
return 0;
}
主要过程:
- 初始化系统相关硬件
- 初始化系统内核对象、例如定时器、调度器、信号
- 创建主线程、定时器线程、idle线程
- 启动调度器
3、创建主线程
在应用层初始函数中调用线程创建函数创建主线程,其中通过条件编译,有两种创建线程的方法:如果使用堆,就用动态的方法来创建线程(rt_thread_create());如果不使用堆,就使用静态的方法来创建线程(rt_thread_init())
第二个参数为函数指针
然后通过rt_thread_startup()启动线程
一旦主线程启动,就会执行main_thread_entry函数,最终就会跳转到用户自己定义的main函数中
线程函数入口:main_thread_entry
栈大小:2048
优先级:10
同等优先级时间片轮询时间:20 个OS Tick rfconfig.h 中配置 :
#define RT_TICK_PER_SECOND 1000 Tick每秒1000次,一次的时间为1ms
//创建线程,线程函数main_thread_entry
tid =rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE,RT_MAIN_THREAD_PRIORITY, 20);
//开启线程 —— 将线程加入到系统的线程队列中,等待系统线程调度器遍历队列调用
rt_thread_startup(tid);
开启线程调度器
//选择优先级最高的线程开始调度
rt_system_scheduler_start();
void main_thread_entry(void*parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
#ifdef RT_USING_SMP
rt_hw_secondary_cpu_up();
#endif
/* invoke system main function */
#if defined(__CC_ARM) ||defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */ //进入用户的main函数入口
#elif defined(__ICCARM__) ||defined(__GNUC__)
main();
#endif
}
注意:
$Sub$ $foo :定义的新功能函数,在foo()函数之前/后使用$Sub$$foo 可以添加一些新的程序代码。
$Super$ $foo :就是原始的未修补的foo函数,使用这个$Super$ $foo函数将直接跳转到foo()函数。