RT-Thread—RTT啓動流程

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函數中只需要創建相應的應用線程即可!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章