ARM官方《CMSIS-RTOS教程》之線程Threads
創建線程Creating Threads
一旦RTOS開始運行,就會有很多系統調用來管理和控制活躍的線程。默認情況下,main()函數自動被創建為第一個可運行的線程。在第一個例子裏我們使用main()函數創建了其他線程,並且隨後讓main()結束運行。然而我們還可以讓main當成一個真正的thread使用。首先,我們需要獲取它的ID號。此時,我們第一個要調用的RTOS函數就是osThreadGetId(),這個函數返回當前運行thread的ID號,並把它存在ID句柄裏。當我們未來某一時刻在OS調用中需要這個線程時,我們使用的就是這個線程的句柄,而非線程的函數名。
-
osThreadId main_id;//創建線程句柄
- void main(void)
- {
- /*獲取main線程的ID號*/
- main_id = osThreadGetId();
- while(1)
- {}
- }
現在我們有了main的ID句柄,就可以創建其他應用線程,並隨後調用osTermainate(main_id)來結束main線程。相比讓一個線程運行到最後一個花括號來結束,這種結束方法更好一些。當然,我們還可以添加while(1)這個循環來繼續使用main函數。
在第一個例子中我們就看到main線程可以作為一個launcher 線程來創建其他應用線程。僅需要兩步就行:第一步,定義線程結構體(同時還可以定義線程傳遞參數)。
- osThreadId thread_id; //線程句柄
- void thread1(void const *argument); //thread1的函數原型
- osThreadDef(thread1, osPriorityNormal, 1, 0); //線程定義結構體
定義線程結構體需要先定義線程函數名、線程優先級、創建線程的實例化個數和它的棧大小。這些參數後續會詳細說明。一旦定義了線程結構體,我們就可以用osThreadCreated() API來創建線程。線程經常在main線程裏創建,當然也可以在其他地方。
thread1_id = osThreadCreated(osThread(thread1), NULL);
上面這條代碼創建線程並啟動它運行。另外,在啟動線程時可以給它傳遞個參數。
- uint32_t startupParameter = 0x23;
- thread1_id = osThreadCreate(osThread(thread1), startupParameter);
當創建線程時,還將給它分配一個棧,用來存儲上下文切換的數據。註意不要和Cortex處理器棧混淆,它只是分配給線程的一段存儲空間。在RTOS配置文件裏已經定義了一個默認的棧大小,當然我們也可以自定義棧的大小。osThreadDef()這個函數的最後一個參數設為0時表示使用默認棧大小。如果需要的話,也可以通過在線程結構體裏定義一個更大的棧來增加額外的存儲資源。
- osThreadDef(thread1, osPriorityNomal, 1, 0); //分配默認棧大小
- osThreadDef(thread2, osPriorityNomal, 1, 1024); //分配1kB的棧大小
當然,如果你分配了更大的棧空間,在RTOS配置文件裏就需要增加額外的存儲空間。
線程的管理和優先級Thread Management and Priority
在創建線程的時候就會給它分配一個優先級。RTOS的調度器靠線程的優先級來決定要調度哪個線程運行。假如同時有幾個線程都準備運行,優先級最高的線程就會進入運行狀態。如果一個高優先級線程處於準備運行狀態,它就會搶占正在運行的低優先級線程。有一點非常重要,高優先級線程占用CPU運行的時候永遠不會停止,除非有RTOS API調用來阻塞它,或者有更高優先級的線程來搶占它。線程的優先級在線程結構體中定義,下面列出可用的優先級定義,其中一般默認的優先級是osPriorityNormal。
CMSIS-RTOS Priority Levels |
---|
osPriorityIdle |
osPriorityLow |
osPriorityBelowNormal |
osPriorityNormal |
osPriorityAboveNormal |
osPriorityHigh |
osPriorityRealTime |
osPriorityError |
一旦線程開始運行,我們就可以使用幾個OS系統調用來管理線程。當然也可以通過其他函數或在它自己代碼裏面提升或者降低它的優先級。
osStatus osThreadSetPriority(threadID, priority);
osPriority osThreadGetPriority(threadID);
在創建線程之後,也可以通過它自己或者其他活躍的線程來刪除它。再強調一次,我們使用thread ID而不是它的函數名來操作。
osStatus = osThreadTermainate(threadID1);
另外,同優先級線程間的切換有一種特殊情況,就是使用協作(co-operative)切換方式:
osStatus osThreadYield();//切換到另外一個準備運行的線程
多重實例化Multiple Instances
RTOS的一個有趣功能就是可以針對一個線程進行多個實例化,例如你可以基於一個線程代碼創建多個用於控制UART的實例,此時,每個UART的實例都會管理一個不同的UART硬件。
首先我們要創建線程結構體,並設置線程實例個數為2:
osThreadDef(thread1, osPriorityNormal, 2, 0);
空閑線程Idle Demon
CMSIS-RTOS提供的最後一個定時器服務函數並不是一個真正的定時器,但是這裏是最合適討論它的地方。如果在我們的RTOS程序裏沒有任何線程正在運行,或者準備運行(舉個例子,所有的線程都處於等待延時函數中),那麽RTOS就會利用空閑的運行時間調用一個“Idle Demon”的線程,這個函數同樣位於RTX_Conf_CM.c文件裏面,空閑線程擁有一個低優先級,只有在沒有其他任何線程準備運行的情況下才會運行。
void os_idle_demon(void)
{
for(;;){
/*此處包含一些用戶的的可選代碼,在沒有線程運行時執行*/
}
}
你可以在這個線程裏加入任何代碼,但是同樣要遵守常規線程的基本準則,一個最簡單的應用就是在此線程中添加控制器MCU的低功耗模式。
void os_idle_demon(void)
{
__wfe();
}
下一步什麽情況取決於MCU的功耗模式選擇,至少CPU會暫停,直到sysTick產生中斷並繼續執行調度器。如果有線程準備運行,CPU就會去執行這個線程,否則,就會重新進入空閑線程,系統也會重新進入睡眠狀態。
線程間通信 Inter-Thread Communication
前面我們已經學習了如何把你的應用代碼設計成獨立的線程,以及如何訪問RTOS的時間服務函數。在實際的工程應用中,線程間的通信是必不可少的,任何一個RTOS都會支持幾種通信方式來連接各種不同的線程。CMSIS-RTOS API支持的通信方式有:信號(signals),信號量(semaphores),互斥鎖(mutexes),郵箱(mailboxes)和消息隊列(message queues)。所有這些首要的核心概念就是並發性。在這一章,將集中討論多任務間的同步問題。
信號 Signals
CMSIS RTOS RTX支持單線程16種信號標誌,這些信號存儲在線程控制塊裏,一個線程會暫停執行,直到一個或一組信號標誌被其他線程置位。
每個線程有16個信號標誌,一個線程可能被置於等待狀態,直到一種模式的信號被其他線程置位。然後此線程就會返回等待狀態,並等待調度器調度到運行狀態。
調用信號等待後,系統會掛起正在運行的線程,並把它置於等待事件(wait_event)狀態,直到所有的信號標誌被置位,線程才會恢復運行。當然也可以加入超時機制,以便讓線程可以順利返回等待狀態。默認初始化超時值是0xFFFF。
osEvent osSignalWait(int32_t signals, uint32_t millisec);
調用osSignalWait之後會把信號復位,如果後面還有信號被置位,線程就可以繼續執行,你可以讀osEvent.value.signals的值來獲取哪個標誌被置位。
任何一個線程都可以置位或清除另外一個線程的信號:
int32_t osSinalSet(osThreadId thread_id, int32_t signals);
int32_t osSignalClear(osThreadId thread_id, int32_t signals);
來源:https://blog.csdn.net/ichamber/article/details/53116253
ARM官方《CMSIS-RTOS教程》之線程Threads