三、RT - thread启动方式
这边以芯海科技的MCU-CS32F031为例:
程序从startup_cs32f031xx.S这个启动文件开始执行。
- 设置系统栈大小,对齐方式,访问权限
;0x00000400用标记Stack_Size表示
Stack_Size EQU 0x00000400
;定义一个名叫STACK的区域 不初始化,可读可写,8字节对齐
AREA STACK, NOINIT, READWRITE, ALIGN=3
;偏移Stack_Size大小
Stack_Mem SPACE Stack_Size
;栈顶地址,因为栈是从顶上而下存取的,只需保存栈顶地址即可
__initial_sp
- 设置系统堆大小,对齐方式,访问权限
Heap_Size EQU 0x00000000
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;不初始化,可读可写,8字节对齐
__heap_base ;堆开始地址
Heap_Mem SPACE Heap_Size ;偏移Heap_Size大小
__heap_limit ;堆结束地址
- 设置中断向量表
;定义名叫RESET的数据段,只读,
AREA RESET, DATA, READONLY
;声明以下3个变量,以便外部文件使用
EXPORT __Vectors ;向量表起始地址
EXPORT __Vectors_End ;向量表结束地址
EXPORT __Vectors_Size ;向量表大小
;设置向量表的内容
__Vectors DCD __initial_sp ; Top of Stack 分配4字节的空间给到栈顶地址
DCD Reset_Handler ; Reset Handler 分配4字节的空间复位中断函数地址
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
;........(省略)
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY ;定义一个名叫只读代码段
Cortex-M3权威指南,在复位后,有如下动作
![](https://i-blog.csdnimg.cn/blog_migrate/26bc5d90bb890bbd51795df2ec4ca48f.png)
由于是从FLASH,那么会进行寄存器的映射。由0x0000000映射到0x08000000,
复位后,1. MCU会从0x08000000的地址中取出栈顶地址MSP
2. MCU会从0x08000004的地址中取出复位中断向量函数地址赋值给PC指针,并执行复位中断函数。
- 复位中断函数的实现
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ;声明复位中断函数
IMPORT __main ;从其他文件中导入main函数
IMPORT SystemInit ;从其他文件中导入SystemInit函数
LDR R0, =SystemInit ;将SystemInit的地址赋值到R0
BLX R0 ;跳转到寄存区reg给出的目的地址。
LDR R0, =__main ;将main函数的地址赋值到R0
BX R0 ;跳转到寄存区reg给出的目的地址。
ENDP ;结束复位中断
这里有两个很有趣的问题,
①__main函数(即是用户程序)在复位中断里面执行,一直没有退出,难道这个合理吗?
解释:Cortex‐M3 支持两种模式和两个特权等级。
两种模式:
Handler 模式
Thread 模式
两个等级:
特权级
用户级
一般情况下中断会处在特权级和Handler 模式,但是复位中断比较例外简单的说就是,他在执行过程中是处在特权级和Thread 模式。其实这个Cortex-M3权威指南有给出说明,
![](https://i-blog.csdnimg.cn/blog_migrate/06f84641be56e4cd3ab26aa8dbc644f2.png)
答案:是的。__main函数会一直在复位中断里面执行,首先,MCU启动时会处在特权级和Handler 模式,但是执行复位中断函数后会变成特权级和Thread 模式。所有__main函数会运行在这个等级和模式中。所以:复位中断(Reset_Handler)和普通中断(SysTick_Handler)的操作模式不一样。
②__main函数和main函数有什么不同呢,为什么会多两个下划线呢?
解释:其实__main函数和main函数时不一样的函数。
调用__main函数比调用main函数多一些功能。以下就是说明__main函数的作用。
调用过程:
![](https://i-blog.csdnimg.cn/blog_migrate/5025a31270e22ae06c3e6e7c42f1c49f.png)
stm32在启动后先进入重启中断函数Reset_Handler,其中会先后调用SystemInit和__main函数,
__main函数属于c库函数,其内部依次进行三步工作,即先初始化rw段,然后初始化zi段,最后调用另一个c库函数__rt_entry(),
__rt_entry()该函数先初始化堆栈和库函数,然后即调用主函数main(),从而进入用户程序。可以看出主函数main()若退出,则在__rt_entry()最后会再调用exit()函数进行退出操作。
答案:stm32启动文件里面Reset_Handler最后调用了__main,而在__main里面最后调用了__rt_entry(),然后__rt_entry()在做完堆栈和库函数初始化工作之后才调用main()。如果没有用__main函数而用main函数则1.2步骤设置的堆栈都白设置了。
- 用户堆栈初始化
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
;如果定义了微库__microLIB, 那么会按1.2步骤初始化堆栈,并声明3个变量方便其他文件使用
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
;否则程序需要外部实现__use_two_region_memory函数,并提供__user_initial_stackheap函数供外部调用
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
讲完了MCU的启动流程,为什么没有看到RT-THREAD的启动呢,也是从main()启动后在执行RT-THREAD的内核吗?
答案是否的。
以下就来真正说明RTTHREAD的启动:
![](https://i-blog.csdnimg.cn/blog_migrate/e0a4cc6235dcd6f1eceacec166a672d8.png)
int $Super$$main(void) 和 int $Sub$$main(void)
是KEIL编译器中可以使用的两个特殊符号。
int $Sub$$main(void) 表示当执行到main();函数时改执行 int $Sub$$main(void) { } 这个函数。
int $Super$$main(void) 表示当执行 $Super$$main()函数时,执行原main(){}定义的的函数。
rtthread则定义了int $Sub$$main(void)函数,所有在执行到__main函数中的main函数时,会改为执行int $Sub$$main(void)函数,即这就是内核函数开始的地方。
/* re-define main function */
int $Sub$$main(void)
{
rtthread_startup(); //调用rtthread_startup()启动函数
return 0;
}
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(); //打印rt-thread-log
/* timer system initialization */
rt_system_timer_init(); //系统时间链表初始化
/* scheduler system initialization */
rt_system_scheduler_init(); //rt-thread调度器初始化 初始化进程链表,优先级链表
/* create init_thread */
rt_application_init(); //创建了main函数线程,并加入调度器
/* timer thread initialization */
rt_system_timer_thread_init(); //rt-thread软件定时器处理线程,用于处理到时后的回调函数
/* idle thread initialization */
rt_thread_idle_init(); //rt-thread空闲线程初始化。
/* start scheduler */
rt_system_scheduler_start(); //启动调度器。
/* never reach here */
return 0;
}
这时因为就只有两个线程main线程和idel线程,main优先级比idle高,所以会从main_thread_entry()开始执行
/* the system main thread */
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
$Super$$main(); /* for ARMCC. */ //执行原main()函数
}
这时rt-thread的启动就写完了。