1.開發背景
? ? ? ?基于上一篇指引,成功創建并啟動線程后,線程已經開始運行了,但是有時我們需要線程暫停運行,例如某個線程是控制 LED 閃燈的,如果現在需要讓 LED 停止工作,單純的關閉 LED 是沒用的,因為下一時刻線程可能就會重新打開 LED 導致程序沒有達到預期,所以線程引入了一個掛起的狀態,如下圖,FreeRTOS 引入了線程的多個狀態。
????????其中包含 Running、Ready、Suspended、Blocked 等狀態。
Running:即運行態,是成功獲取了 CPU 使用權的線程。
Ready:即準備態,單核 CPU 在同一時刻只能做一件事情,所以如果當前有其他線程獲取了 CPU 的使用權(一般是高優先級線程),這個時候等待運行的線程就是準備態。
Suspended:即掛起態,正如上文說到的,如果我們想讓某個線程暫時停止工作,就可以掛起對應線程,被掛起的線程就是掛起態。
Blocked:即阻塞態,因為線程存在高低優先級,如果高優先級線程一直運行會導致低優先級線程一直搶占不到 CPU 的使用權,如果使用掛起的方式去掛起高優先級線程,那么高優先級線程的實時性就會大打折扣,所以就引入了阻塞的概念,高優先級線程可以一直阻塞在某個事件,在阻塞期間會讓出 CPU 的使用權,但是一旦高優先級線程滿足指定事件就會立刻搶占低優先級線程的 CPU 使用權,這樣就保證了高優先級線程的實時性。
????????上述純粹個人理解,如有誤請見諒,可以參考鏈接:FreeRTOS task states and state transitions described
2.開發需求
? ? ? ? 掛起、恢復和刪除已有線程
3.開發環境
? ? ? ? window10 + MDK + STM32F429 + FreeRTOS10.3.1
4.實現步驟
4.1 線程掛起其他線程
1)創建控制線程和 2 個測試線程
/* 測試初始化 */
void aTest_Init(void)
{/* 創建動態任務 */xTaskCreate(TaskCtrl, "TaskCtrl", 500, NULL, 5, &p->taskCtrl);/* 共用一個任務函數 創建多個任務 */static char whichTask[TASK_LIST_SIZE][3] = {0};for (int i = 0; i < TASK_LIST_SIZE; i++){snprintf(whichTask[i], 2, "%d", i);xTaskCreate(TaskList, "TaskList", 500, (void*)whichTask[i], 5, &p->taskList[i]);}
}
2)測試線程循環打印
/* 動態任務組 */
static void TaskList(void *pvParameters)
{int count = 0;int whichTask = atoi(pvParameters);Log_Debug("%s [%d]\r\n", __func__, whichTask);for ( ; ; ){vTaskDelay(1000);Log_Debug("%s [%d] count = %d\r\n", __func__, whichTask, count++);}
}
3)控制線程間斷掛起和恢復,使用 vTaskSuspend 掛起線程,vTaskResume 恢復線程。
/* 動態任務 */
static void TaskCtrl(void *pvParameters)
{Log_Debug("%s\r\n", __func__);/* 掛起線程 0 */vTaskDelay(3000);vTaskSuspend(p->taskList[0]);vTaskDelay(3000);vTaskResume(p->taskList[0]);for ( ; ; ){vTaskDelay(1000);}
}
4)測試結果
如圖所示,可以看出線程0 中間有 3 個周期是停止工作的。
4.2 線程掛起線程本身
1)基于上面試驗的基礎上,修改測試線程為打印日志后掛起自身,這里做這個試驗是為了驗證?vTaskSuspend 如果傳入參數為 NULL,即指向線程本身。
/* 動態任務組 */
static void TaskList(void *pvParameters)
{int count = 0;int whichTask = atoi(pvParameters);Log_Debug("%s [%d]\r\n", __func__, whichTask);for ( ; ; ){vTaskDelay(1000);Log_Debug("%s [%d] count = %d\r\n", __func__, whichTask, count++);vTaskSuspend(NULL);}
}
2)控制線程只需定期恢復線程即可
/* 動態任務 */
static void TaskCtrl(void *pvParameters)
{Log_Debug("%s\r\n", __func__);/* 掛起線程 0 */vTaskDelay(3000);vTaskResume(p->taskList[0]);for ( ; ; ){vTaskDelay(1000);}
}
3)測試結果
如圖所示,測試線程執行了一次就掛起了本身,控制線程間隔 3s 之后喚醒了一次測試線程0
4)源碼解析
vTaskSuspend
????????->?prvGetTCBFromHandle
/** Several functions take an TaskHandle_t parameter that can optionally be NULL,* where NULL is used to indicate that the handle of the currently executing* task should be used in place of the parameter. This macro simply checks to* see if the parameter is NULL and returns a pointer to the appropriate TCB.*/
#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? pxCurrentTCB : ( pxHandle ) )
如源碼所示,如果傳入指針為空,即獲取當前控制塊?pxCurrentTCB
4.3 中斷中恢復線程
1)基于上面的實驗使用中斷來代替控制線程來恢復已掛起的測試線程
/* Key2 PC13 Key0 PH3 Key1 PH2 */
void Exti13_TriggerInterrupt(void)
{if (mspGpio_GetInput("PC13") == 0){Log_Debug("%s 按鍵執行測試線程0 恢復\r\n", __func__);BaseType_t xYieldRequired;xYieldRequired = xTaskResumeFromISR(p->taskList[0]);portYIELD_FROM_ISR(xYieldRequired);mspExti_Close(13);}
}
在中斷中使用 FreeRTOS 接口需要帶 FromISR 后綴的,如 xTaskResumeFromISR,需要portYIELD_FROM_ISR 切換上下文,否則實時性會收到一定的影響,為了調試和演示方便在中斷中打印了數據,在實際項目中切記不要在中斷中停留,特別是打印等高延時操作。
2)測試結果
如圖所示,在中斷中恢復已經掛起的線程也是可以的。
4.4 線程刪除
1)線程刪除后會釋放內存,由于現在的線程都是在系統堆棧動態開辟的,所以線程刪除后內存會回歸系統內存堆棧。
/* 動態任務 */
static void TaskCtrl(void *pvParameters)
{Log_Debug("%s\r\n", __func__);/* 掛起線程 0 */vTaskDelay(3000);Log_Info("FreeRTOS Remain Space = %d Bytes\r\n", xPortGetFreeHeapSize());vTaskDelete(p->taskList[0]);Log_Info("Delete Task0 And FreeRTOS Remain Space = %d Bytes\r\n", xPortGetFreeHeapSize());vTaskDelete(p->taskList[1]);Log_Info("Delete Task1 And FreeRTOS Remain Space = %d Bytes\r\n", xPortGetFreeHeapSize());for ( ; ; ){vTaskDelay(1000);
// Log_Debug("%s\r\n", __func__);}
}
2)測試結果
如圖所示,刪除2個任務后,系統內存由 49472Bytes -> 51584Bytes -> 53696Bytes