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的基本執行單位
- 任務具有任務函式、優先順序、堆疊空間等特性