1. 程式人生 > >實時作業系統的任務排程示例之優先順序反轉

實時作業系統的任務排程示例之優先順序反轉

1 什麼是優先順序反轉? 目前市面流行的實時作業系統都是採用可搶佔式的基於優先順序的排程方式,其保證了處於就緒狀態的優先順序高的任務可以先於優先順序低的任務而執行。但這並不是絕對的,優先順序反轉是實時系統中的一個經典特例。其大體流程如下: 假設系統中有3個task,優先順序分別為高、中、低,以task_high,task_middle,task_low代稱 1) 一開始,task_high,task_middle處於掛起狀態,task_low在執行 2) task_low要訪問共享資源,獲取了一個訊號量semaphore 3) 這之後task_high等待的事件到來,task_high搶佔了task_low開始執行,在執行過程中,也需要訪問同一共享資源,所以也要去拿semaphore,由於它已經被task_low取得,所以task_high再次被掛起。
4) task_low繼續執行
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,兩者之間的優先順序調換了,反轉
了一樣。
2 優先順序反轉會有什麼危害?

乍一看上面的這個執行流程,很多同學可能覺得,不就是有個task本來想讓它早點執行,結果因為一個意外,晚一點點執行了嗎,能有什麼後果?

是的,如果上面案例中task_middle佔用的時間較短,可能問題還不大,但是如果運氣不好,task_middle執行的是一個耗時的操作,或者task_middle不是一個任務,而是一堆優先順序處於task_low和task_high之間,正好在這期間需要執行呢?那那task_low始終無法釋放訊號量,task_high也要被delay很久才能獲得CPU。

嵌入式實時作業系統,最重要的指標就是:確保重要的任務執行時間是可預測的,有一個不能容忍的deadline,要確保任何時刻都不能超過某個時間

。考慮一下汽車的電子系統裡控制受撞擊後彈出安全氣囊的task,如果執行時間被delay會有什麼後果。

在有些場景下,會導致整個系統崩潰,有一個高大上的典型案例,美國的火星探路者號在一次執行任務時,就是因為重要任務被有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之後