實時作業系統的任務排程示例之優先順序反轉
5) task_middle等待的事件這時候到來,它從task_low手裡搶來了CPU,開始執行 6) task_middle執行結束或再次掛起,讓出CPU 7) task_low繼續執行,完成共享資源的訪問,釋放semaphore 8) task_high獲得semaphore,得以繼續執行。 task_middle的優先順序是低於task_high的,原則上應該是task_high優先獲得CPU得到執行。但是在這個過程中,由於共享資源的競爭訪問,導致了高優先順序的任務並不能被優先排程 執行的順序看起來好像是高優先順序任務task_high和中優先順序任務task_middle,兩者之間的優先順序調換了,反轉
乍一看上面的這個執行流程,很多同學可能覺得,不就是有個task本來想讓它早點執行,結果因為一個意外,晚一點點執行了嗎,能有什麼後果?
是的,如果上面案例中task_middle佔用的時間較短,可能問題還不大,但是如果運氣不好,task_middle執行的是一個耗時的操作,或者task_middle不是一個任務,而是一堆優先順序處於task_low和task_high之間,正好在這期間需要執行呢?那那task_low始終無法釋放訊號量,task_high也要被delay很久才能獲得CPU。
嵌入式實時作業系統,最重要的指標就是:確保重要的任務執行時間是可預測的,有一個不能容忍的deadline,要確保任何時刻都不能超過某個時間
在有些場景下,會導致整個系統崩潰,有一個高大上的典型案例,美國的火星探路者號在一次執行任務時,就是因為重要任務被有delay,結果導致了系統復位、資料丟失。有興趣的朋友可以google一下 What Happened on Mars?
3 優先順序反轉的示例 本節參考第一節的描述設計了一個簡單的實驗,來演示優先順序反轉的執行過程,使用的rtos是Nucleus。 應用建立了3個task,其優先順序分別為149,150,151,對應的task名分別為task_high,task_middle和task_low 建立2個訊息佇列,讓task_high和task_middle可以阻塞在上面並可在適當時間將它們喚醒 還有一個訊號量,模擬task_high和task_low訪問共享資源時的互斥 NU_Create_Queue(&sync_queue1,"queue1", queue_Buf1, QUEUE_SIZE, NU_FIXED_SIZE, 1, NU_FIFO); NU_Create_Queue(&sync_queue2,"queue2", queue_Buf2, QUEUE_SIZE, NU_FIXED_SIZE, 1, NU_FIFO);NU_Create_Semaphore(&sync_sema,"sync_sema", 1, NU_PRIORITY);
NU_Create_Task(&highTask, "",task_high, 0, NU_NULL, Task_Stack1, Stack_Size, 149, 0,NU_PREEMPT, NU_START); NU_Create_Task(&middleTask, "" ,task_middle, 0, NU_NULL, Task_Stack2,Stack_Size, 150, 0,NU_PREEMPT, NU_START); NU_Create_Task(&lowTask, "",task_low, 0, NU_NULL, Task_Stack3, Stack_Size, 151, 0,NU_PREEMPT, NU_START); static int a = 100,b = 50,tmp; void swap_tmp() //純粹是為了演示延遲的函式,因為如果就寫for迴圈空轉可能會被編譯器優化掉 { tmp = a; a = b; b = tmp; } void task_high(UNSIGNED argc, VOID* argv) { u32 msg,msg_len; printf("task_high start: priority %d",((TC_TCB*)TCT_Current_Thread())->tc_priority); NU_Receive_From_Queue(&sync_queue1,&msg,1,&msg_len,NU_SUSPEND); printf("task_high. obtain semaphore: %d",tc_priority); NU_Obtain_Semaphore(&sync_sema,NU_SUSPEND); for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_high. do works"); } void task_middle(UNSIGNED argc, VOID* argv) { u32 msg,msg_len; u32 i; printf("task_middle start: priority %d",((TC_TCB*)TCT_Current_Thread())->tc_priority); NU_Receive_From_Queue(&sync_queue2,&msg,1,&msg_len,NU_SUSPEND); for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_middle. do works");
}
void task_low(UNSIGNED argc, VOID* argv) { int cnt; printf("task_low start: priority %d,obtain semaphone",((TC_TCB*)TCT_Current_Thread())->tc_priority); NU_Obtain_Semaphore(&sync_sema,NU_SUSPEND); printf("task_low. do1-round works");
for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_low. send message to wakeup task_high"); NU_Send_To_Queue(&sync_queue1,&cnt,1,NU_SUSPEND); printf("task_low. do2-round works"); for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_low. send message to wakeup task_middle"); printf("task_low. do 3-round works"); for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_low. release semaphore"); NU_Release_Semaphore(&sync_sema); }
輸出的log結果如下所示,很顯然task_high的do works比task_middle的do works要晚得到執行:
[0:0:16:373] task_high start: priority 149
[0:0:16:383] task_middle start: priority 150
[0:0:16:388] task_low start: priority 151,obtain semaphore
[0:0:17:033] task_low. do 1-round works
[0:0:18:323] task_low. send message to wakeup task_high
[0:0:18:323] task_high. obtain semaphore: 149
[0:0:18:968] task_low. do 2-round works
[0:0:20:257] task_low. send message to wakeup task_middle
[0:0:20:902] task_middle. do works
[0:0:22:837] task_low. do 3-round things
[0:0:24:127] task_low. release semaphore
[0:0:24:773] task_high. do works
4 怎麼避免優先順序反轉? 避免優先順序反轉是RTOS設計者的任務,樓主手上有3個rtos的執行環境,FreeRTOS裡有實現優先順序繼承式的方法,ucosii和Nucleus都沒有內建避免優先順序反轉的方法。這讓樓主困惑不已,作為一個成熟的商業核心,居然沒有FreeRTOS在細節上更為完善。樓主在Nucleus裡做了一個簡單的workround,以優先順序繼承的方式避免了發生優先順序反轉。(對“優先順序繼承”不瞭解的同學請自行百度) 在訊號量的結構體SM_SCB裡增加一個欄位TC_TCB* sm_own_task。用來儲存當前擁有訊號量的task指標 NU_Obtain_Semaphore的函式實現裡,如果能成功獲得訊號的分支,加上一句
semaphore->sm_own_task = (TC_TCB*)TCT_Current_Thread(); task_high函式修改如下,在獲取訊號量之前,將task_low的優先順序暫時提升至與task_high一樣,獲得訊號量之後再將其恢復。 void task_high(UNSIGNED argc, VOID* argv) { u32 msg,msg_len; TC_TCB* ptcb = NULL; DATA_ELEMENT old_priority; printf("task_high start: priority %d",((TC_TCB*)TCT_Current_Thread())->tc_priority); NU_Receive_From_Queue(&sync_queue1,&msg,1,&msg_len,NU_SUSPEND); printf("task_high. obtain semaphore:"); if (sync_sema.sm_own_task->tc_priority > ((TC_TCB*)TCT_Current_Thread())->tc_priority) { printf("task_high.Improve temporary priority"); ptcb = sync_sema.sm_own_task; old_priority = ptcb ->tc_priority; TCS_Change_Priority(ptcb,((TC_TCB*)TCT_Current_Thread())->tc_priority); } NU_Obtain_Semaphore(&sync_sema,NU_SUSPEND); if (ptcb) { printf("task_high.restore temporary priority"); TCS_Change_Priority(ptcb,old_priority); }
for (i=0;i<0xffffff;i++) swap_tmp(); printf("task_high. do works"); } 執行起來輸出的log如下:
[0:0:16:389] task_high start: priority149
[0:0:16:399] task_middle start: priority 150
[0:0:16:405] task_low start: priority 151,obtain semaphone
[0:0:17:050] task_low. do 1-round works
[0:0:18:339] task_low. send message to wakeup task_high
[0:0:18:339] task_high. obtain semaphone: 149
[0:0:18:339] task_high. Improve temporary priority //臨時提升了task_low的優先順序
[0:0:18:984] task_low. do 2-round works
[0:0:20:274] task_low. send message to wakeup task_middle
[0:0:20:918] task_low. do 3-round things
[0:0:22:208] task_low. release semaphore
[0:0:22:208] task_high. restore temporary priority //task_low的優先順序恢復
[0:0:22:853] task_high. do works
[0:0:26:080] task_middle. do works //task_middle的執行順序排在了task_high之後