0、思考與回答 0.1、思考一 為什麼要增加時間片輪詢? 目前的 RTOS 內核已經支持搶占優先順序,即高優先順序的任務會搶占低優先順序的任務得到執行,但是對於同等優先順序的任務,如果不支持時間片輪詢,則只能有一個任務運行,並且由於優先順序相同所以除延時阻塞到期外也不會發生任務調度,因此需要增加時間片輪詢保證 ...
0、思考與回答
0.1、思考一
為什麼要增加時間片輪詢?
目前的 RTOS 內核已經支持搶占優先順序,即高優先順序的任務會搶占低優先順序的任務得到執行,但是對於同等優先順序的任務,如果不支持時間片輪詢,則只能有一個任務運行,並且由於優先順序相同所以除延時阻塞到期外也不會發生任務調度,因此需要增加時間片輪詢保證同等優先順序的任務能得到輪流執行
1、內核程式修改
1.1、xTaskIncrementTick( )
在該函數中除了任務延時阻塞時間到期產生任務調度外,增加支持時間片輪詢的任務切換,具體如下所示
/*task.c*/
BaseType_t xTaskIncrementTick(void)
{
// 省略未修改的程式
......
#if((configUSE_PREEMPTION == 1) && (configUSE_TIME_SLICING == 1))
// 支持時間片輪詢
if(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[pxCurrentTCB->uxPriority])) > 1)
{
xSwitchRequired = pdTRUE;
}
#endif
return xSwitchRequired;
}
/* FreeRTOSConfig.h */
// 支持時間片輪詢
#define configUSE_TIME_SLICING 1
1.2、原理
假設當前系統中只存在兩個優先順序相同的任務,時間片輪詢任務切換流程如下
xTaskIncrementTick()
-> vTaskSwitchContext()
-> taskSELECT_HIGHEST_PRIORITY_TASK()
-> listGET_OWNER_OF_NEXT_ENTRY()
當進入滴答定時器中斷服務函數時,如果發現就緒鏈表數組中的某個鏈表中鏈表項的數量大於 1 ,則表示該優先順序下有不止一個任務,此時就可以產生任務調度
當有任務調度產生的時候,會調用 vTaskSwitchContext()
和 taskSELECT_HIGHEST_PRIORITY_TASK()
兩個函數尋找當前的最高優先順序任務,但是系統中只有兩個優先順序相同的任務,因此最高優先順序仍然沒變,但是在這個優先順序下返回的任務卻變成了下一個
為什麼呢?
關鍵在於 listGET_OWNER_OF_NEXT_ENTRY()
函數,這個巨集函數每次調用會獲取鏈表中下一個鏈表項的 pvOwner
參數,由於是雙向鏈表,因此會不斷的迴圈鏈表中的鏈表項(兩個同等優先順序的任務),每次發生任務調度就會切換一次任務,所以就實現了時間片輪詢
3、實驗
3.1、測試
參考 FreeRTOS 簡單內核實現6 優先順序 "3.1、測試" 小節內容,將兩個任務的優先順序修改為一樣,然後在兩個任務中均使用軟體延時模擬任務連續運行,具體程式如下所示
/* main.c */
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
// 軟體延時
void delay(uint32_t count)
{
for(;count!=0;count--);
}
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
UBaseType_t Task1Priority = 2;
TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
UBaseType_t Task2Priority = 2;
// 任務 1 入口函數
void Task1_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
delay(10000000);
}
}
// 任務 2 入口函數
void Task2_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);
delay(10000000);
}
}
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// 創建任務 1 和 2
Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,
(char *)"Task1",
(uint32_t)TASK1_STACK_SIZE,
(void *)NULL,
(UBaseType_t)Task1Priority,
(StackType_t *)Task1Stack,
(TCB_t *)&Task1TCB);
Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,
(char *)"Task2",
(uint32_t)TASK2_STACK_SIZE,
(void *) NULL,
(UBaseType_t)Task2Priority,
(StackType_t *)Task2Stack,
(TCB_t *)&Task2TCB );
// 啟動任務調度器,永不返回
vTaskStartScheduler();
/* USER CODE END 2 */
將 configUSE_TIME_SLICING
調整為 0 ,然後燒錄程式,仍然使用邏輯分析儀捕獲兩個 LED 的引腳電平,結果如下圖所示
可以發現在不啟用時間片輪詢時,由於兩個任務優先順序一致,並且兩個任務模擬連續運行,因此只有任務 Task2 被運行 (為什麼是 Task2 ?)
將 configUSE_TIME_SLICING
調整為 1,然後燒錄程式,仍然使用邏輯分析儀捕獲兩個 LED 的引腳電平,結果如下圖所示
可以發現,對於優先順序相同且連續運行的任務幾乎是在同時運行,其實是因為每個時間片(滴答定時器間隔)都發生了一次任務調度
3.2、待改進
當前 RTOS 簡單內核已實現的功能有
- 靜態方式創建任務
- 手動切換任務
- 臨界段保護
- 任務阻塞延時
- 支持任務優先順序
- 阻塞鏈表
- 時間片輪詢
後續 RTOS 簡單內核可以增加
- 任務間通信機制
- 信號量
- 互斥鎖
- 消息隊列
- ......
- 其他功能
- 軟體定時器
- ......