文章目錄
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_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;
}
重點是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函數中只需要創建相應的應用線程即可!