1. 程式人生 > >FreeRTOS(23)---FreeRTOS 空閒任務分析

FreeRTOS(23)---FreeRTOS 空閒任務分析

FreeRTOS 空閒任務分析

FreeRTOS 空閒任務分析

當RTOS排程器開始工作後,為了保證至少有一個任務在執行,空閒任務被自動建立,佔用最低優先順序(0優先順序)。

   xReturn = xTaskCreate( prvIdleTask,
                         "IDLE",configMINIMAL_STACK_SIZE,
                         (void * ) NULL,
                         (tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                         &xIdleTaskHandle);

空閒任務是FreeRTOS不可缺少的任務,因為FreeRTOS設計要求必須至少有一個任務處於執行狀態。我們來看一下空閒任務要做的工作。

釋放記憶體

從V9.0版本開始,如果一個任務刪除另外一個任務,被刪除任務的堆疊和TCB立即釋放。如果一個任務刪除自己,則任務的堆疊和TCB和以前一樣,通過空閒任務刪除。所以空閒任務開始就會檢查是否有任務刪除了自己,如果有的話,空閒任務負責刪除這個任務的TCB和堆疊空間。

處理空閒優先順序任務

當使用搶佔式核心,相同優先順序的任務使用時間片方式獲得CPU許可權。如果有任務與空閒任務共享一個優先順序,並且巨集configIDLE_SHOULD_YIELD設定為1,那麼空閒任務不必等到時間片耗盡再進行任務切換。

所以空閒任務檢查空閒優先順序下的就緒列表中是否有多個任務,有的話則執行任務切換,讓使用者任務獲得CPU許可權。

巨集configIDLE_SHOULD_YIELD控制任務在空閒優先順序中的行為。僅在滿足下列條件後,才會起作用。

  1. 使用搶佔式核心排程
  2. 使用者任務使用空閒優先順序。

通過時間片共享同一個優先順序的多個任務,如果共享的優先順序大於空閒優先順序,並假設沒有更高優先順序任務,這些任務應該獲得相同的處理器時間。

但如果共享空閒優先順序時,情況會稍微有些不同。當configIDLE_SHOULD_YIELD為1時,其它共享空閒優先順序的使用者任務就緒時,空閒任務立刻讓出CPU,使用者任務執行,這樣確保了能最快響應使用者任務。處於這種模式下也會有不良效果(取決於你的程式需要),描述如下:
在這裡插入圖片描述


圖中描述了四個處於空閒優先順序的任務,任務A、B和C是使用者任務,任務I是空閒任務。上下文切換週期性的發生在T0、T1…T6時刻。當用戶任務執行時,空閒任務立刻讓出CPU,但是,空閒任務已經消耗了當前時間片中的一定時間。這樣的結果就是空閒任務I和使用者任務A共享一個時間片。使用者任務B和使用者任務C因此獲得了比使用者任務A更多的處理器時間。

可以通過下面方法避免:

  1. 如果合適的話,將處於空閒優先順序的各單獨的任務放置到空閒鉤子函式中;
  2. 建立的使用者任務優先順序大於空閒優先順序;
  3. 設定IDLE_SHOULD_YIELD為0;

設定configIDLE_SHOULD_YIELD為0將阻止空閒任務為使用者任務讓出CPU,直到空閒任務的時間片結束。這確保所有處在空閒優先順序的任務分配到相同多的處理器時間,但是,這是以分配給空閒任務更高比例的處理器時間為代價的。

執行空閒任務鉤子函式

空閒任務鉤子是一個函式,這個函式由使用者來實現,RTOS規定了函式的名字和引數,這個函式在每個空閒任務週期都會被呼叫。

要建立一個空閒鉤子:

  1. 設定FreeRTOSConfig.h 檔案中的configUSE_IDLE_HOOK 為1;
  2. 定義一個函式,函式名和引數如下所示:
void vApplicationIdleHook(void ); 

這個鉤子函式不可以呼叫會引起空閒任務阻塞的API函式(例如:vTaskDelay()、帶有阻塞時間的佇列和訊號量函式),在鉤子函式內部使用協程是被允許的。

使用空閒鉤子函式設定CPU進入省電模式是很常見的。

低功耗tickless模式

通常情況下,FreeRTOS回撥空閒任務鉤子函式(需要設計者自己實現),在空閒任務鉤子函式中設定微處理器進入低功耗模式來達到省電的目的。因為系統要響應系統節拍中斷事件,因此使用這種方法會週期性的退出、再進入低功耗狀態。如果系統節拍中斷頻率過快,則大部分電能和CPU時間會消耗在進入和退出低功耗狀態上。

FreeRTOS的tickless空閒模式會在空閒週期時停止週期性系統節拍中斷。停止週期性系統節拍中斷可以使微控制器長時間處於低功耗模式。移植層需要配置外部喚醒中斷,當喚醒事件到來時,將微控制器從低功耗模式喚醒。微控制器喚醒後,會重新使能系統節拍中斷。由於微控制器在進入低功耗後,系統節拍計數器是停止的,但我們又需要知道這段時間能折算成多少次系統節拍中斷週期,這就需要有一個不受低功耗影響的外部時鐘源,即微處理器處於低功耗模式時它也在計時的,這樣在重啟系統節拍中斷時就可以根據這個外部計時器計算出一個調整值並寫入RTOS 系統節拍計數器變數中。

空閒任務的原始碼如下所示,其中巨集portTASK_FUNCTION翻譯出來為:void prvIdleTask(void * pvParameters)。

static portTASK_FUNCTION( prvIdleTask,pvParameters )
{
	 /*防止編譯器警告 */
	 (void ) pvParameters;

	 for(;; )
	 {
		   /*檢查是否有任務刪除了自己,如果有的話,空閒任務負責刪除這個任務的TCB和堆疊空間 */
		   prvCheckTasksWaitingTermination();

		   #if( configUSE_PREEMPTION == 0 )
		   {
				/*如果我們沒有使用搶佔式排程,我們會強制任務切換,看看是否有其它任務變得有效.
				如果使用搶佔式排程,是不需要這樣的,因為任務變得有效後會搶佔空閒任務.*/
				taskYIELD();
		   }
		   #endif/* configUSE_PREEMPTION */

		   #if( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
		   {
				/*  當使用搶佔式核心,相同優先順序的任務使用時間片方式獲得CPU許可權.如果有任務與空閒
				任務共享一個優先順序,那麼空閒任務不必等到時間片耗盡再進行任務切換.
				如果空閒優先順序下的就緒列表中有多個任務,則執行使用者任務*/
				if(listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) >( UBaseType_t ) 1 )
				{
						 taskYIELD();
				}
		   }
		   #endif/* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 )) */

		   #if( configUSE_IDLE_HOOK == 1 )
		   {
				externvoid vApplicationIdleHook( void );

				/*呼叫使用者定義函式.這樣允許設計者在不增加任務開銷的情況下實現後臺功能
				注意:這個函式中絕對不允許呼叫任務可能引起阻塞的函式.*/
				vApplicationIdleHook();
		   }
		   #endif/* configUSE_IDLE_HOOK */

		   #if( configUSE_TICKLESS_IDLE != 0 )
		   {
		   TickType_txExpectedIdleTime;

				/*如果每次執行空閒任務都掛起排程器,起然後再解除排程器,這很難讓人滿意,因此這裡
				執行兩次同樣的比較(xExpectedIdleTime和configEXPECTED_IDLE_TIME_BEFORE_SLEEP),
				第一次比較是測試一下是否達到預期的空閒時間,並不會掛起排程器.*/
				xExpectedIdleTime= prvGetExpectedIdleTime();

				if(xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
				{
					 vTaskSuspendAll();
					 {
						   /*現在排程器被掛起,需要再次取樣空閒時間,這次空閒時間可以使用了*/
						   configASSERT(xNextTaskUnblockTime >= xTickCount );
						   xExpectedIdleTime= prvGetExpectedIdleTime();

						   if(xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
						   {
								portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime );
						   }
					 }
					 (void ) xTaskResumeAll();
				}
		   }
		   #endif/* configUSE_TICKLESS_IDLE */
	 }
}