1. 程式人生 > >FreeRTOS筆記(四)初識任務

FreeRTOS筆記(四)初識任務

文章目錄


上一文連結:FreeRTOS筆記(三)配置檔案FreeRTOSConfig.h

01 - 單任務與多工

1.1 - 單任務系統

  微控制器裸機程式設計的時候,都會用一個while(1)去包裹所有應用程式,換言之,裸機程式設計中的應用程式就執行在一個死迴圈while(1)中,而且各個應用程式比如串列埠、LED、顯示屏等都是排隊輪流執行的(中斷除外),只要前面的程式沒有完成,後面的程式就必須等待。

  這種裸機程式設計非常容易實現,對於順序執行的邏輯可以控制得很好,但是當外部資源龐大,某些應用程式會在某個不可預測的時間上需要實時性執行

,此時裸機程式設計就無能為力了,裸機程式設計的系統一般稱為單任務系統,顯然現在需要多工系統,任務的概念就更加清晰了。

1.2 - 多工系統

  多工系統假若在單核版上執行(嵌入式中往往是單核),那麼意味著每個時刻只有一個任務在進行,下一個時刻到底選擇哪個任務去執行?需要一個掌控全域性任務執行時間安排的角色:排程器。排程器的基本功能是使得任務可以切出和切入,在某個任務需要馬上執行的時候立刻打斷當前正在執行的任務,這樣就使得系統具有實時性。排程器的存在也使得任務具有多種特性,比如優先順序、堆疊空間等等。

02 - 建立和刪除任務

2.1 - FreeRTOS_API

函式 描述
xTaskCreate() 堆疊由FreeRTOS動態分配
xTaskCreateStatic() 堆疊由使用者指定分配
xTaskCreateRestricted() 堆疊由FreeRTOS動態分配,使用MPU(記憶體保護單元)進行限制
xTaskDelete() 刪除任務

  根據FreeRTOS提供的API,能夠知道任務具有的基本特性,這些特性需要在建立的時候確定。

2.2 - 任務函式

  首先了解怎樣建立和刪除一個任務,上述提到,任務應該具有一個死迴圈函式,除了函式,任務還有優先順序、堆疊空間等特性,而這個函式稱為任務函式,它告訴任務需要執行的內容,任務函式既然是死迴圈,就不需要返回值,於是返回值就是void,那麼引數呢?FreeRTOS規定任務函式的引數是一個指標,基型別是void(void*指標可以指向任一個型別的指標,理解為包羅永珍的盒子,任何東西都能放進去),任務函式的原型如下:

void ATaskFunction( void *pvParameters );

2.3 - 建立任務

  這個任務函式需要繫結在任務中才能被執行,而任務需要建立,FreeRTOS提供任務相關的API,建立任務的API有3種,常用的是xTaskCreate(),不同的API用途在上表檢視,xTaskCreate()的原型及需要的標頭檔案如下:

#include “FreeRTOS.h”
#include “task.h”

aseType_t xTaskCreate( TaskFunction_t pvTaskCode,			//任務函式
                        const char * const pcName,		//任務名稱
                        unsigned short usStackDepth,		//堆疊大小
                        void *pvParameters,				//任務函式的引數
                        UBaseType_t uxPriority,			//任務優先順序
                        TaskHandle_t *pxCreatedTask );	//任務控制代碼

  引數和返回值可以在官方手冊中檢視,目前暫時理解部分。值得注意的是,任務的建立可以在排程器啟動前,也可以是排程器啟動後。

2.4 - 刪除任務

  任務函式執行在死迴圈之內,不能以任何方式返回,如果任務函式實在不需要繼續執行,那麼可以使用vTaskDelete()API函式主動或者被動刪除任務。任務刪除後就不再執行,變成了“殭屍”,等待著核心收屍vTaskDelete()的原型及需要的標頭檔案如下:

#include “FreeRTOS.h”
#include “task.h”

void vTaskDelete( TaskHandle_t pxTask );	//任務控制代碼

03 - 堆疊空間

  FreeRTOS多工系統需要排程器進行任務的切換,而被切出的任務下一次可以被完整地繼續執行的原因在於,每個任務都有自己的堆疊,排程器進行任務切換的時候保護切出任務當前的堆疊現場,當下一次切入的時候再恢復現場,這樣任務中的資料就不會被破壞。

  在建立任務的時候需要指出堆疊的大小,這對於一般的開發者而言很難估計,目前只需要寫入一箇中等大的值即可,如果嵌入式單板的RAM資源短缺,就需要非常仔細地計算每個任務的堆疊大小。任務堆疊的大小由參賽 unsigned short usStackDepth指定,單位是字,不是位元組。

04 - 任務優先順序

  排程器選擇任務執行的依據是任務的優先順序,FreeRTOS的任務優先順序是0~(configMAX_PRIORITIES-1)configMAX_PRIORITIES在FreeRTOSConfig.h(可以看上一文)中定義,0是最低優先順序,數值越大優先順序越高。
  排程器選擇任務需要有一個排程演算法,排程器總是選擇優先順序最高的任務去執行,如果任務隊伍中的優先順序一樣,就採用時間片的方法法輪流執行,每個任務執行一段時間,隨後切換同級的其它任務。

05 - 任務的基礎測試

  接下來進行任務的一些基礎測試,先測試一些任務的基礎特性,真實感受FreeRTOS的任務執行,比如建立刪除、堆疊大小和優先順序,配合官方的一些API進行使用,任務的建立僅僅測試xTaskCreate()函式。

5.1 - 建立和刪除測試

  • 1、建立1個任務,串列埠間隔輸出running,同時輸出當前的任務數
#define START_TASK_PRIO                 1       //優先順序
#define STATR_STACK_SIZE                128     //堆疊大小
TaskHandle_t StartTask_Handler;                 //任務控制代碼

//任務函式
void start_task(void *pParam)
{
    for(;;)
    {
        printf("task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
        vTaskDelay(1000);
    }
}

void main(void)
{
	printf("before create task : %ld\r\n",uxTaskGetNumberOfTasks());
	//建立任務
	xTaskCreate( (TaskFunction_t)start_task,
	             (const char*)"start_task",
	             (uint16_t)STATR_STACK_SIZE,
	             (void*)NULL,
	             (UBaseType_t)START_TASK_PRIO,
	             (TaskHandle_t*)&StartTask_Handler
	);          
	printf("after create task : %ld\r\n",uxTaskGetNumberOfTasks());
	//開啟排程器
	vTaskStartScheduler();
}
程式碼 執行結果

  這裡產生疑問,為什麼有3個任務?從輸出可知,排程器啟動前只有1個任務,啟動後有3個,意味著排程器建立了2個任務,這2個是什麼任務?開啟啟動排程器函式vTaskStartScheduler()檢視原始碼:

void vTaskStartScheduler( void )
{
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        ……
        xIdleTaskHandle = xTaskCreateStatic(); 
        ……
    }
    #else
    {
        ……
        xReturn = xTaskCreate(); 
        ……
    }
    #endif 
    ……
    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif 
    ……
}

  問題解決了,這2個任務,一個是IDLE空閒任務,一個是定時器任務,空閒任務是一定會建立的,因為if…else…都有建立任務的函式,而定時器任務是根據巨集configUSE_TIMERS來建立的,這個巨集在FreeRTOSConfig.h中配置,上一篇文章中配置了這個巨集,所以就會有3個任務。

  • 2、建立2個任務A和B,A任務串列埠輸出running和當前的任務數,然後刪除自己,B任務間隔輸出running,同時輸出當前的任務數,並檢查任務A的狀態
void A_task(void *pParam)
{
    printf("A task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
    
    vTaskDelete(NULL);
}   

void B_task(void *pParam)
{
    eTaskState state;
    for(;;)
    {
        printf("B task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
        state = eTaskGetState(ATask_Handler);
        if(state == eDeleted)
        {
            printf("B task running, A is delete\r\n");
        }
        else
        {
            printf("B task running, A is other\r\n");   
        }
        vTaskDelay(1000);
    }
}   
程式碼 執行結果

  能夠看到,排程器開啟後串列埠輸出混亂,這是資源訪問的問題,暫時不理會。還是能夠觀測到,任務A輸出了資訊,此時還有4個任務(A、B、空閒任務和定時器任務),當A刪除後,只有B在執行,並顯示A任務已經刪除,目前只有3個任務。

5.2 - 堆疊大小測試

  • 建立1個任務,堆疊大小從1往上設定,串列埠間隔輸出running和變數值
#define START_TASK_PRIO					1		//優先順序
#define STATR_STACK_SIZE				16			//堆疊大小
TaskHandle_t StartTask_Handler;					//任務控制代碼
void start_task(void *pParam)
{
	for(;;)
	{
		printf("task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
		vTaskDelay(1000);
	}
}
程式碼 執行結果

  當任務堆疊不足夠的時候,任務是無法執行的。而且整個系統都會停止,具體原因後續瞭解。

5.3 - 任務的優先順序測試

  • 建立2個任務A和B,A優先順序比B高,都在串列埠間隔輸出running
/* A 任務 */
#define A_TASK_PRIO					2		//優先順序
#define A_STACK_SIZE				128			//堆疊大小
TaskHandle_t ATask_Handler;					//任務控制代碼


/* B 任務 */
#define B_TASK_PRIO					1		//優先順序
#define B_STACK_SIZE				128			//堆疊大小
TaskHandle_t BTask_Handler;					//任務控制代碼

void A_task(void *pParam)
{
	for(;;)
	{
		printf("A task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
	}
}	

void B_task(void *pParam)
{
	for(;;)
	{
		printf("B task running, task number: %ld\r\n",uxTaskGetNumberOfTasks());
	}
}	
程式碼 注意,這裡不能使用vTaskDelay()延遲函式,因為vTaskDelay()會使得任務阻塞,那麼其它任務就可以執行 執行結果

  在前面任務建立與刪除的測試中,A、B兩個任務的優先順序是相同的,於是排程器採用時間片排程法,任務輪流執行,時間片是多少?可以通過FreeRTOSConfig.h檔案去配置,預設情況下我們可知,A、B任務還沒有完整輸出串列埠的時候就被切換了,所以出現了亂碼。但是現在任務A永遠是可以執行的最高優先順序任務,任務B則永遠得不到執行

06 - 總結

  • 單任務系統不能處理實時性的任務需要
  • 多工系統中任務的切換需要排程器的存在
  • 排程器的基本功能是任務的切出和切入
  • 任務是FreeRTOS的基本執行單位
  • 任務具有任務函式、優先順序、堆疊空間等特性