RTOS的俩种启动方式 RTOS有俩种主流的启动方式: 1.将准备工作(包括创建线程)都做好后,启动RTOS调度器 2.先创建一个启动线程并启动RTOS调度器,在启动线程中创建其他应用线程 俩种方式有着截然不同的思路,下面简单介绍一下俩种启动方式的流程: 方式一:先创建所有线程,再调度 在main函数中完成以下任务: 初始化硬件 初始化RTOS系统 创建应用线程 启动RTOS调度器 通过伪代码来简单描述一下启动流程: int main( void ) { /* 硬件初始化 */ HardWare_Init(); /* RTOS系统初始化 */ RTOS_Init(); /* 创建应用线程 */ RTOS_ThreadCreate( Thread1 ); RTOS_ThreadCreate( Thread2 ); …… /* 启动RTOS调度器 */ RTOS_Start(); } 方式二:在启动线程中,创建线程 启动步骤如下: 在main函数中初始化硬件 在main函数中初始化RTOS系统 在main函数中创建启动线程 在main函数中开启RTOS调度 在启动线程中创建应用线程 通过伪代码来简单描述一下启动流程: int main( void ) { /* 初始化硬件 */ HardWare_Init(); /* 初始化RTOS系统 */ RTOS_Init(); /* 创建启动线程 */ RTOS_ThreadCreate( AppThreadStart ); /* 启动RTOS调度器 */ RTOS_Start(); } void AppThreadStart( void *arg ) { /* 创建应用线程 */ RTOS_ThreadCreate( Thread1 ); RTOS_ThreadCreate( Thread2 ); …… /* 关闭启动线程 */ RTOSThreadClose( AppThreadStart ); } 注意:在起始线程中创建完应用线程后要,关闭起始线程。 ucos第一种第二种启动方法都可以;freertos和RTT默认使用第二种。 RTT的启动流程 RTT中使用的是第二种启动方式,也就是先创建一个启动线程并开启系统调度,然后在启动线程中创建其他应用线程。上面说了,一些初始化工作和创建启动线程的工作都在main函数中完成,可实际中RTT例程中main函数中并没有做这些工作!!! RTT的实际启动流程比上面讲的高级一点,大体流程如下: 系统上电后首先执行启动文件中的复位函数 复位函数最后一步会调用C库的__main函数 在__main函数中初始化系统堆栈 执行完__main函数后跳转到$Sub$$main函数 在$Sub$$main函数中按照上面方式二进行配置 通过$Super$$main函数跳转到main函数中 在main函数中创建应用线程 所以RTT的启动流程相比于之前的,只是把方式二的操作放在$Sub$$main函数中执行了,下面捋一捋RTT的具体启动流程: 1.上电后首先执行的复位函数 复位函数是由汇编语言写的,代码如下: Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main CPSID I ; 关中断 LDR R0, =0xE000ED08 LDR R1, =__Vectors STR R1, [R0] LDR R2, [R1] MSR MSP, R2 LDR R0, =SystemInit BLX R0 CPSIE i ; 开中断 LDR R0, =__main ;在这里进入__main函数 BX R0 ENDP 关于汇编语言之前整理过一部分,链接:汇编语言基本知识 其余的代码和RTT启动流程没啥关系。 2.调用__main函数 __main函数主要就是初始化一下系统堆栈,然后在函数的最后跳转到$Sub$$main函数,由于__main函数中的其他部分与RTT启动流程没关系,所以就不讲了,__main函数主要就是一个过渡的作用,从复位函数过渡到$Sub$$main函数。 3.main函数的预操作 $Sub$$函数与$Super$$函数是编译器(此处是KEIL)自带的一对扩展函数,它们的作用就是对要扩展的函数执行预操作,比如$Sub$$main就是在main函数执行前,执行的函数,执行完$Sub$$main函数后,用$Super$$main函数跳转到main函数中。 了解$Sub$$main函数的原理后,来看一下RTT中$Sub$$main函数的代码: $Sub$$main函数 int $Sub$$main( void ) { /* 关闭中断 */ rt_hw_interrupt_disable(); /* 启动RTT */ rtthread_startup(); return 0; } $Sub$$main函数中是通过rtthread_startup()函数来启动RTT的。 rtthread_startup()函数 int rtthread_startup( void ) { /* 关中断,在硬件初始化前习惯性的关中断 */ rt_hw_interrupt_disable(); /* 硬件初始化 */ rt_hw_board_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; } 重点是rt_application_init()函数,因为创建启动线程就在此函数中进行,要注意的是rt_application_init()函数只是对启动线程进行创建,并没有启动系统调度,系统调度是在最后才启动的。 rt_application_init()函数 void rt_application_init(void) { rt_thread_t tid; #ifdef RT_USING_HEAP /* 静态创建启动线程 */ tid = rt_thread_create("main", main_thread_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20); RT_ASSERT(tid != RT_NULL); #else rt_err_t result; /* 动态创建启动线程 */ tid = &main_thread; result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL, main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20); RT_ASSERT(result == RT_EOK); /* if not define RT_USING_HEAP, using to eliminate the warning */ (void)result; #endif rt_thread_startup(tid); } 创建启动线程有静态创建和动态创建俩种方法,具体的方式可以根据宏定义来选择。 可以看到启动线程的人口函数是main_thread_entry(),下面再看一下main_thread_entry()函数。 main_thread_entry函数 void main_thread_entry( void *parameter ) { extern int main( void ); extern int $Super$$main( void ); /* RTT组件初始化 */ rt_components_init(); /* 跳转到main函数 */ $Super$$main(); } 4.main函数中创建线程 int main(void) { /* * 开发板硬件初始化, RT-Thread 系统初始化已经在 main 函数之前完成, * 即在 component.c 文件中的 rtthread_startup()函数中完成了。 (1) * 所以在 main 函数中,只需要创建线程和启动线程即可。 */ thread1 = /* 线程控制块指针 */ rt_thread_create("thread1", /* 线程名字,字符串形式 */ thread1_entry, /* 线程入口函数 */ RT_NULL, /* 线程入口函数参数 */ HREAD1_STACK_SIZE, /* 线程栈大小,单位为字节 */ THREAD1_PRIORITY, /* 线程优先级,数值越大,优先级越小 */ THREAD1_TIMESLICE); /* 线程时间片 */ if (thread1 != RT_NULL) rt_thread_startup(thread1); else return -1; thread2 = /* 线程控制块指针 */ rt_thread_create("thread2", /* 线程名字,字符串形式 */ thread2_entry, /* 线程入口函数 */ RT_NULL, /* 线程入口函数参数 */ THREAD2_STACK_SIZE, /* 线程栈大小,单位为字节 */ THREAD2_PRIORITY, /* 线程优先级,数值越大,优先级越小 */ THREAD2_TIMESLICE); /* 线程时间片 */ if (thread2 != RT_NULL) rt_thread_startup(thread2); else return -1; (4) thread3 = /* 线程控制块指针 */ rt_thread_create("thread3", /* 线程名字,字符串形式 */ thread3_entry, /* 线程入口函数 */ RT_NULL, /* 线程入口函数参数 */ THREAD3_STACK_SIZE, /* 线程栈大小,单位为字节 */ THREAD3_PRIORITY, /* 线程优先级,数值越大,优先级越小 */ THREAD3_TIMESLICE); /* 线程时间片 */ if (thread3 != RT_NULL) rt_thread_startup(thread3); else return -1; /* 执行到最后,通过 LR 寄存器执行的地址返回 */ (5) } 在main函数中只需要创建相应的应用线程即可!
原作者:Aspirant-GQ
|