FreeRTOS筆記(五)任務狀態
文章目錄
上一文連結:FreeRTOS筆記(四)初識任務
01 - 任務的狀態
任務被建立後,它可能正在執行,可能暫停執行,任務有狀態之分是由於排程器的存在,排程器需要決定哪些任務可以去執行,於是在FreeRTOS中任務具有4種狀態,分別是就緒態、執行態、阻塞態和掛起態,它們之間的轉化關係如下:
4個狀態的含義如下:
就緒態:已經可以執行,等待排程器的切入
執行態:正在佔用CPU執行
阻塞態
掛起態:退出排程系統,排程器不可見,只能使用vTaskSuspend()掛起和vTaskResume()喚醒後進入就緒態
雖然官方文件只描述4個狀態,但小白認為應該是5種狀態,第5種是殭屍態,指在任務被刪除後,其TCB控制塊扔保留一段時間,等待核心檢查和回收資源,在核心沒有處理之前,任務其實並沒有被完全刪除,但是再也不能被排程器排程,這稱為殭屍態,在Linux下的程序是存在殭屍態的,而從FreeRTOS的API中就可以看出,FreeRTOS的任務也存在殭屍態。
要使用eTaskGetState()
函式,就要在FreeRTOSConfig.h中配置INCLUDE_eTaskGetState
#define INCLUDE_eTaskGetState 1
02 - tick時鐘和排程器
排程器本身也是一段程式,任務需要排程器安排執行順序,那麼排程器本身就需要被執行,這個執行由一個稱為心跳時鐘(tick)的中斷觸發,tick時鐘的頻率需要在FreeRTOSConfig.h檔案中配置,單位是HZ,比如本例程中配置configTICK_RATE_HZ
為1000,那麼中斷頻時間是1000/1000 = 1ms
,每隔1ms,FreeRTOS就會進入tick中斷,觸發排程器進行工作。
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
排程器被觸發後,會根據事先設定好的排程演算法進行工作,FreeRTOS使用的排程演算法有優先順序搶佔式排程和協作式排程:
優先順序搶佔式排程演算法,給每一個任務分配一個優先順序,排程器每次都選擇優先順序最高的任務執行,如果優先順序相同,就採用時間片輪流執行,每個任務執行一個時間片後切出,再切入下一個任務。
協作式排程演算法,只可能在執行態任務進入阻塞態或是執行態任務顯式呼叫taskYIELD()
主動讓出CPU時,才會進行上下文切換。
排程演算法的配置也是在FreeRTOSConfig.h中進行。
#define configUSE_PREEMPTION 1 //0-協作排程,1-搶佔式排程
03 - 任務狀態測試
下面用優先順序搶佔式排程演算法來測試一下任務的各種狀態,其中掛起和喚醒的API如下:
專案 | Value |
---|---|
vTaskSuspend() | 掛起一個任務 |
vTaskResume() | 喚醒一個任務 |
測試內容:一共有5個任務,分別是start和ABCD,start優先順序最高,其餘優先順序遞增,任務的工作如下:
start:負責建立另外4個任務,輸出所有任務的狀態,然後刪除自己
A:輸出所有任務的狀態,開啟LED
B:輸出所有任務的狀態,關閉LED,使用vTaskDelay()
阻塞自己
C:輸出所有任務的狀態使用vTaskDelay()
阻塞自己,然後間隔使用vTaskResume
喚醒D
D:輸出所有任務的狀態,使用vTaskSuspend()
掛起自己
/* start 任務 */
void start_task(void *pParam)
{
//進入臨界區
taskENTER_CRITICAL();
xTaskCreate( (TaskFunction_t)a_task,
(const char*)"a_task",
(uint16_t)A_STACK_SIZE,
(void*)NULL,
(UBaseType_t)A_TASK_PRIO,
(TaskHandle_t*)&aTask_Handler
);
xTaskCreate( (TaskFunction_t)b_task,
(const char*)"b_task",
(uint16_t)B_STACK_SIZE,
(void*)NULL,
(UBaseType_t)B_TASK_PRIO,
(TaskHandle_t*)&bTask_Handler
);
xTaskCreate( (TaskFunction_t)c_task,
(const char*)"c_task",
(uint16_t)C_STACK_SIZE,
(void*)NULL,
(UBaseType_t)C_TASK_PRIO,
(TaskHandle_t*)&cTask_Handler
);
xTaskCreate( (TaskFunction_t)d_task,
(const char*)"d_task",
(uint16_t)D_STACK_SIZE,
(void*)NULL,
(UBaseType_t)D_TASK_PRIO,
(TaskHandle_t*)&dTask_Handler
);
print_state("start",eTaskGetState(StartTask_Handler));
print_state("A",eTaskGetState(aTask_Handler));
print_state("B",eTaskGetState(bTask_Handler));
print_state("C",eTaskGetState(cTask_Handler));
print_state("D",eTaskGetState(dTask_Handler));
printf("\r\n");
//退出臨界區
taskEXIT_CRITICAL();
//刪除自己
vTaskDelete(NULL);
}
/* A 任務 */
void a_task(void *pParam)
{
for(;;)
{
print_all_state("A");
//開啟LED
GPIO_ResetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
}
}
/* B 任務 */
void b_task(void *pParam)
{
for(;;)
{
//輸出
print_all_state("B");
//關閉LED
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
//阻塞自己
vTaskDelay(10);
}
}
/* C 任務 */
void c_task(void *pParam)
{
for(;;)
{
//輸出
print_all_state("C");
//阻塞自己
vTaskDelay(10);
//喚醒D
vTaskResume(dTask_Handler);
}
}
/* D 任務 */
void d_task(void *pParam)
{
for(;;)
{
//輸出
print_all_state("D");
//掛起自己
vTaskSuspend(NULL);
}
}
其中print_all_state()和print_state()是為了方便輸出狀態,程式碼如下:
static void print_state(char *msg,eTaskState state)
{
printf("%s-",msg);
switch(state)
{
case eRunning: printf("run");break;
case eReady: printf("ready");break;
case eBlocked: printf("block");break;
case eSuspended:printf("suspend");break;
case eDeleted: printf("delete");break;
default: printf("5");break;
}
printf(" ");
}
static void print_all_state(char *msg)
{
taskENTER_CRITICAL();
printf("%s:",msg);
print_state("start",eTaskGetState(StartTask_Handler));
print_state("A",eTaskGetState(aTask_Handler));
print_state("B",eTaskGetState(bTask_Handler));
print_state("C",eTaskGetState(cTask_Handler));
print_state("D",eTaskGetState(dTask_Handler));
printf("\r\n");
taskEXIT_CRITICAL();
}
執行結果
因為延遲太短,看不到LED的閃爍,但是從串列埠可以看出,各個任務有序執行,每個狀態都出現了,狀態之間的轉換幾乎在一瞬間,執行時序圖可以簡述如下:
04 - 總結
- 任務有4種(5種)狀態,分別為就緒態、執行態、阻塞態、掛起態(殭屍態)
- 排程器程式在每個tick時鐘中斷裡被執行
- FreeRTOS排程器有兩種排程演算法,搶佔式和協作式