从本篇开始正式进入RT-Thread的开发学习,参考RT-Thread快速入门实例教程对RT-Thread作以初步了解,探究RT-Thread启动代码及用户入口函数。
1.工程结构
2.用户入口代码
上一节中,application文件夹中存放main.c文件,所以学习RTT的源码从这个文件入手,由浅到深,打开这个文件后可以看到里面内容很简单:
#include <rtthread.h>
int main(void)
{
/* user app entry */
return 0;
}
在这里不禁会有所疑惑,作为一个RTOS,main函数中竟然只有一行注释和return 0,are you kiding me?其实,正如那一句精辟的注释,user app entry,用户应用程序入口,这个main函数并不是我们平常所接触到的main函数,这个main函数其实是由RTT呈现给我们的main函数,我们可以在此,在RTT操作系统之上编写我们的代码,即用户代码。
3.RT-Thread启动代码
了解了rtt的用户入口代码之后,再来看看RT-Thread是如何启动的?最好的办法是使用MDK的软件模拟debug功能,单步执行一遍程序,答案自然知晓。
首先编译工程,然后更改Debug模式为软件模拟模式,然后ctrl+F5进入debug模式,点击左上角复位按钮,将系统置于复位状态,因为RTT使用USART1打印信息,所以需要调出USART1串口便于观察(在View->Serial Windows->UART#1),接下来按F11就可以单步执行程序;
3.1.汇编启动文件
这个启动文件应该很熟悉了,无论裸机编程还是RTOS编程都是必不可少的,它的作用是构建好单片机上C语言的运行环境(主要是堆栈)
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0 /* 跳转执行系统时钟初始化 */
LDR R0, =__main
BX R0 /* 跳转执行C语言中的main函数 */
ENDP
3.2.启动文件跳转到的main函数
在上一步的汇编启动文件中跳转到main函数中,我们可以发现,它跳转到了RT-Thread源码中的"components.c"文件的main函数中,具体代码如下:
#if defined (__CC_ARM)
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
/* 关闭中断系统 */
rt_hw_interrupt_disable();
/* 启动RT-Thread系统 */
rtthread_startup();
return 0;
}
可以看到,这才是整个RTOS系统的核心main函数,依然很简洁,精辟,第一行代码关闭整个中断系统,然后第二行代码启动RT-Thread系统;
这里需要注意一点:在定义main函数的时候采用了<pre>不能识别此Latex公式: Sub</pre>
<pre>不能识别此Latex公式: main,这是ARM链接器的特殊语法,因为之前用户入口函数是main,此处不能再次main,所以可以采用</pre>
Sub<pre>不能识别此Latex公式: </pre>
在不修改函数名的情况下对main函数的主体重新定义,在进入 main 程序之前调用另外一个例程完成系统功能初始化,具体说明可以参见ARM官方文档。Linker User Guide
接下来我们继续执行代码:
3.2.1.关闭中断系统
程序跳转到了汇编编写的“ context_rvds.S”文件中,执行以下几行代码,关闭整个中断系统:
MRS r0, PRIMASK
CPSID I
BX LR
3.2.2.启动RT-Thread系统
单步执行后系统再次跳转到"components.c"文件中,rtthread_startup()函数的代码如下,由注释可以看到,中断系统对于RTOS的影响很大,所以在启动之前再次关闭中断系统,然后便是整个RTT的启动流程:
int rtthread_startup(void)
{
/* 再次关闭中断系统 */
rt_hw_interrupt_disable();
/* 板级初始化 */
rt_hw_board_init();
/* 打印rt版本号 */
rt_show_version();
/* 定时器系统初始化 */
rt_system_timer_init();
/* 调度器系统初始化 */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* 信号量系统初始化 */
rt_system_signal_init();
#endif
/* 创建初始化线程 */
rt_application_init();
/* 定时器线程初始化 */
rt_system_timer_thread_init();
/* 空闲线程初始化 */
rt_thread_idle_init();
/* 启动调度器 */
rt_system_scheduler_start();
/* 永远不会执行到这儿 */
return 0;
}
至此,我们对RTT的启动过程和我们需要的用户入口函数有了大致的认识,接下来我们不再深入RT-Thread的内核底层,而是 在RT-Thread的基础上学习如何应用编程,毕竟原理上的理解是为了更好的应用,在学习完RT-Thread的基本应用之后,我们再回过头来深入底层一探内核究竟,便于我们在一些特殊的应用里对内核进行高度裁剪。