freeRTOS
支持多個任務具有相同的優先級,因此,當它被配置為可搶占內核時,調度算法既支持基於優先級的調度,也支持時間片輪流調度。任何時候調度器運行時它都選擇處於就緒狀態下的優先級最高的那個任務;如果有多個任務處於同一優先級,則freertos每個時鍾節拍的中斷服務程序中,將對這些任務應用換調度算法,輪流執行這些任務。
系統用uxTopReadyPriority全局變量記錄當前處於就緒態的任務的最高優先級。調度的時候就根據這個uxTopReadyPriority直接找到就緒鏈表中pxReadyTasksLists[ uxTopReadyPriority ]的任務,進行運行。
一個任務可以通過調用 taskYIELD() 讓出cpu,從而調度令一個任務運行。它的實現如下:
#define taskYIELD() portYIELD()
而portYIELD()是一個體系結構相關的函數,對於不同的mcu需要實現這么一個函數完成調度。我拿atmel的atmega323 mcu為例子,說明下具體實現。
extern void vPortYield( void ) __attribute__ ( ( naked ) ); #define portYIELD() vPortYield() * Manual context switch. The first thing we do is save the registers so we * can use a naked attribute. void vPortYield( void ) __attribute__ ( ( naked ) ); void vPortYield( void ) { portSAVE_CONTEXT(); vTaskSwitchContext(); portRESTORE_CONTEXT(); asm volatile ( "ret" ); }
portYIELD() 就是vportYield(),它保存現場,然后調用vTaskSwitchContext()這個函數選擇下一個運行的任務,然后portRESTORE_CONTEXT()完成任務切換。
void vTaskSwitchContext( void ) { traceTASK_SWITCHED_OUT(); if( uxSchedulerSuspended != ( unsigned portBASE_TYPE ) pdFALSE ) { /* 當前調度器被禁止,因此不允許調度,設xMissedYield=TRUE*/ xMissedYield = pdTRUE; return; } taskCHECK_FOR_STACK_OVERFLOW(); /* 找到包含有就緒任務的最高優先級隊列 */ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) ) { --uxTopReadyPriority; } /* listGET_OWNER_OF_NEXT_ENTRY 從最高優先級隊列上取下一個任務,設為pxCurrentTCB,即馬上將要切換到該任務運行*/ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,&( pxReadyTasksLists[ uxTopReadyPriority ] ) ); traceTASK_SWITCHED_IN(); vWriteTraceToBuffer(); }
這里注意的是listGET_OWNER_OF_NEXT_ENTRY()宏並不是簡單的從隊列中取下第一個任務,而是walk through這個隊列,比如上一次調度它從這個隊列上取下的是第一個任務,那么這次調度選中的則是該隊列中的第2個任務。這樣就保證了同一優先級的多個任務之間公平的平分處理器時間。選中任務后(用pxCurrentTCB指向它)。那么在portRESTORE_CONTEXT()中就完成最后的切換。因此這個地方有些有趣,函數 vTaskSwitchContext () 從名稱看給人感覺是完成任務切換的,但是其實並不是這樣,它只完成選擇下一個運行的任務(也就是將要切換過去的任務),真正的切換時在portRESTORE_CONTEXT()中就完成的。
任務調度還可以發生在時鍾節拍中斷isr中,這個當然也是與cpu體系結構相關的。仍然以atmega323為例。它用的是定時器1的比較中斷A作為時鍾節拍產生器。其中斷isr是:
void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal, naked ) ); void SIG_OUTPUT_COMPARE1A( void ) { vPortYieldFromTick(); asm volatile ( "reti" ); }
而vPortYieldFromTick()就是完成調度。代碼如下:
void vPortYieldFromTick( void ) __attribute__ ( ( naked ) ); void vPortYieldFromTick( void ) { // 保存現場 portSAVE_CONTEXT(); /* 檢查延時任務鏈表,如果發現有任務延時已經到期,則將該任務加到就緒鏈表*/ vTaskIncrementTick(); // 挑選下一個運行的任務,准備切換過去 vTaskSwitchContext(); // 完成任務切換 portRESTORE_CONTEXT(); asm volatile ( "ret" ); }