芯片启动到main函数之前的运行过程
不论是否有RTOS,芯片的启动过程是一致的,均是要从复位向量处取得上电复位后要执行的第一个语句,接下来进行系统时钟初始化等工作,随后跳转到main处。
寻找第一条被执行的指令的存放处
源程序生成机器码的基本过程‘
要将C语言源程序编程可以下载到MCU中运行的机器码,需要经过预编译、编译、汇编、链接等过程,这一切都是开发环境IDE自动完成的。
深入理解启动过程
总流程
启动过程大致如下:
1、板级硬件初始化:SysTick定时器配置、堆内存初始化等
2、定时器初始化
3、调度器初始化
4、设置并创建线程
5、设置并创建空闲线程
6、启动调度器
在目前的RTOS中,主要有两种主流的启动方式
万事俱备,只欠东风
这种方法是在main函数中将硬件初始化、RTOS系统初始化,所有线程的创建这些都弄好,这个简称为万事俱备,只欠东风。最后只欠一道东风,即启动RTOS的调度器,开始多线程的调度,具体的伪代码实现见下面
int main(void)
{
/*硬件初始化*/
HardWare_Init(); (1)
/*RTOS初始化*/
RTOS_Init(); (2)
//创建线程1,但线程1不会运行,因为调度器还没有开启 (3)
RTOS_ThreadCreate(Task1);
//创建线程2,但线程2不会运行,因为调度器还没有开启
RTOS_ThreadCreate(Task2);
...
//继续创建各种线程
//启动RTOS,开始调度 (4)
RTOS_Start();
}
void Thread1(void *parameter (5)
{
while(1)
{
//线程实体,必须有阻塞的情况出现
}
}
void Thread2(void *parameter) (6)
{
while(1)
{
//线程实体,必须有阻塞的情况出现
}
}
1)硬件初始化
2)RTOS系统初始化
3)创建各种线程
4)启动RTOS调度器
5)6)线程实体通常都是一个不带返回值的无限循环的C函数
小心翼翼,十分谨慎
第二种我称之为小心翼翼,十分谨慎法,这种方法是在main函数中将硬件和RTOS系统初始化好,然后创建一个启动线程后就启动调度器,然后再启动线程里面在创建各种应用线程,当所有的线程都创建好之后,启动线程把自己删除,具体的伪代码实现如下
int main(void)
{
//硬件初始化
HardWare_Init(); (1)
//RTOS系统初始化
RTOS_Init() (2)
//创建一个线程
RTOS_ThreadCreate(AppThreadStart); (3)
//启动RTOS,开始调度
RTOS_Start(); (4)
}
void AppThreadCreate(void *arg) (5)
{
//创建线程1,然后执行
RTOS_ThreadCreate(Task1);
//创建线程2,然后执行
RTOS_ThreadCreate(Task2) (6)
...
//创建各种线程
//当创建完各种线程,关闭起始线程
RTOSThread_Close(AppThreadStart); (7)
}
void Thread1(void *parameter (8)
{
while(1)
{
//线程实体,必须有阻塞的情况出现
}
}
void Thread2(void *parameter) (9)
{
while(1)
{
//线程实体,必须有阻塞的情况出现
}
}
RT-Thread和FreeRTOS默认使用第二种启动方式
当你拿到一个RT-Thread工程时,你会发现在main函数里只有创建线程和启动线程的代码,硬件初始化和系统初始化以及启动调度器等信息看不到,那是因为RT-Thread拓展了main函数,在main函数之前做好了这些工作。
我们知道,在系统上电复位第一个执行的是启动文件的由汇编编写的复位函数Reset_Handler,复位函数最后会调用__main,__main函数的主要工作就是初始化系统的堆和栈,最后调用C库函数,最终去到C的世界。
复位函数代码
这里如果你进入仿真模式下,复位之后单步调试的话,会发现执行完汇编会跳到components.c中如下位置,而不是去到main函数,这是为什么?因为RT-Thread使用编译器(这里针对的是MDK),自带的$Sub$$和$Super$$这两个符号拓展了main函数。使用$Sub$$可以在执行main之前先执行$Sub$$main,在$Sub$$main中我们可以先执行一些预操作,当做完这些预操作之后,最终还是要执行main函数,这个就通过调用$Super$$main来实现。当需要扩展的函数不是main时,只需要将main换成你要拓展的函数名即可,即$Sub$$function和$Sub$$function。
$Sub$$main函数
知道了 $Sub$$和 $Super$$的用法之后,我们回到文件中可以看到关闭中断,除了硬件中断FAULT和NMI可以响应外,其他统统关掉,该函数时在接口文件中由汇编实现的。
rtthread_startup()函数
rt_application_init()函数
其中main线程的入口函数main_thread_entry如下
$Super$$main()函数