1. FreeRTOS 調度機制概述
FreeRTOS 是一個實時操作系統(RTOS),其核心功能是通過?調度器(Scheduler)?管理多個任務的執行。調度機制決定了?何時切換任務?以及?如何選擇下一個運行的任務,以滿足實時性、優先級和資源共享的需求。以下是 FreeRTOS 調度機制的全面解析:
1.1.調度器的基本工作原理
FreeRTOS 調度器的主要職責是:
任務切換(Context Switching):保存當前任務的上下文(寄存器、棧等),并恢復下一個任務的上下文。
任務選擇:根據任務狀態(就緒、阻塞、掛起)和優先級,決定下一個運行的任務。
事件響應:處理中斷、信號量、隊列等事件觸發的任務狀態變化。
調度觸發條件
主動讓出 CPU:任務調用?
taskYIELD()
?或阻塞(如?vTaskDelay()
、xQueueReceive()
)。系統心跳(Tick 中斷):
SysTick
?或定時器中斷觸發調度檢查。中斷服務程序(ISR):某些中斷(如?
PendSV
)強制觸發任務切換。
1.2?調度策略
FreeRTOS 支持兩種主要調度策略:
(1) 搶占式調度(Preemptive Scheduling)
默認模式,高優先級任務可搶占低優先級任務。
特點:
高優先級任務就緒時,立即獲得 CPU。
適用于硬實時系統(如電機控制、傳感器采集)。
void vHighPriorityTask(void *pvParams) {while (1) {printf("High Priority Task Running!\n");vTaskDelay(100); // 短暫阻塞,讓出 CPU}
}void vLowPriorityTask(void *pvParams) {while (1) {printf("Low Priority Task Running...\n");vTaskDelay(1000); // 長時間阻塞}
}
?結果:vHighPriorityTask
?會頻繁搶占?vLowPriorityTask
。
(2) 協作式調度(Cooperative Scheduling)
需手動調用?
taskYIELD()
?讓出 CPU,無搶占。特點:
任務必須主動放棄 CPU,否則一直運行。
適用于簡單系統,但實時性較差。
1.3 任務狀態與調度
FreeRTOS 任務可能處于以下狀態:
狀態 | 描述 |
---|---|
就緒(Ready) | 任務準備就緒,等待調度器分配 CPU。 |
運行(Running) | 當前正在執行的任務(單核 CPU 同一時間只有一個)。 |
阻塞(Blocked) | 任務因等待事件(如延時、信號量、隊列)而暫停,不占用 CPU。 |
掛起(Suspended) | 任務被顯式掛起(vTaskSuspend() ),不參與調度,需手動恢復(vTaskResume() )。 |
調度器選擇任務的規則:
優先選擇?最高優先級的就緒任務。
同優先級任務按?時間片輪轉(Round-Robin)?分配 CPU(需啟用?
configUSE_TIME_SLICING
)。
1.4 調度器實現細節
(1) 任務切換的底層機制
PendSV 中斷:
FreeRTOS 使用?PendSV
(可掛起的系統調用)進行任務切換,確保切換過程?原子化,避免在中斷中直接切換導致嵌套問題。上下文保存與恢復:
硬件自動保存部分寄存器(
R0-R3
,?R12
,?LR
,?PC
,?xPSR
)。軟件手動保存剩余寄存器(
R4-R11
)。
任務切換流程:
觸發?
PendSV
?中斷。保存當前任務的寄存器到其棧中。
調用?
vTaskSwitchContext()
?選擇新任務。從新任務的棧恢復寄存器。
返回新任務繼續執行。
(2) 調度器啟動
調用?vTaskStartScheduler()
?后:
創建?空閑任務(Idle Task)(優先級 0,用于清理資源)。
初始化系統心跳(
SysTick
?定時器)。開始調度第一個任務。
2?FreeRTOS 中的 Tick 中斷詳解
Tick 中斷(系統心跳中斷)是 FreeRTOS 調度和任務管理的核心機制,它通過周期性中斷為系統提供時間基準,驅動任務調度、延時管理和超時檢測。以下是 Tick 中斷的全面解析:
2.1 Tick 中斷的作用
(1) 核心功能
提供系統時間基準:
維護全局時鐘計數器?xTickCount
,記錄系統啟動后的 Tick 次數(1 Tick =?configTICK_RATE_HZ
?分之一秒)。任務延時管理:
檢查阻塞任務的超時時間(如?vTaskDelay()
),喚醒到期任務。調度觸發:
在搶占式調度模式下,每個 Tick 中斷會檢查是否需要切換任務(如同優先級任務時間片輪轉)。軟件定時器:
驅動 FreeRTOS 的軟件定時器(xTimer
)回調。
(2) 典型應用場景
周期性任務(如每 100ms 采集一次傳感器數據)。
超時控制(如等待信號量時設置超時時間)。
低功耗模式(Tickless 模式下動態調整中斷間隔)。
2.2 Tick 中斷的硬件實現
(1) 硬件依賴
定時器選擇:通常使用 MCU 的?
SysTick
?定時器(Cortex-M 內核內置),也可用通用定時器(如 TIMx)。中斷頻率:由?
configTICK_RATE_HZ
?定義(通常 100Hz~1kHz)。
示例配置(FreeRTOSConfig.h
):c
復制
下載
#define configTICK_RATE_HZ 1000 // 1kHz Tick,每1ms中斷一次
(2) 初始化流程
FreeRTOS 啟動時(vTaskStartScheduler()
)會初始化 Tick 中斷:
配置定時器周期(如?
SysTick_Config(SystemCoreClock / configTICK_RATE_HZ)
)。設置中斷優先級(通常為最低優先級,避免影響其他中斷)。
啟用定時器和中斷。
2.3 Tick 中斷的處理流程
(1) 中斷服務程序(ISR)
當 Tick 中斷發生時:
遞增時鐘計數器:
xTickCount++
。檢查任務延時列表:
遍歷阻塞任務列表,若有任務延時到期,則將其移至就緒列表。
觸發調度(可選):
若啟用搶占式調度(
configUSE_PREEMPTION=1
),檢查是否需要任務切換。
處理軟件定時器(可選):
若啟用?
configUSE_TIMERS=1
,檢查定時器回調。
(2) 關鍵代碼(Cortex-M 示例)
void SysTick_Handler(void) {// 1. 進入中斷上下文portENTER_CRITICAL(); // 關中斷(防止嵌套)// 2. 調用 FreeRTOS 的 Tick 處理函數if (xTaskIncrementTick() != pdFALSE) {portYIELD(); // 觸發任務切換(通過 PendSV)}// 3. 退出中斷portEXIT_CRITICAL();
}
2.4 Tick 中斷與任務調度
(1) 時間片輪轉(Round-Robin)
同優先級任務共享 CPU:
每個 Tick 中斷會檢查當前任務是否用完時間片(configUSE_TIME_SLICING=1
),若用完則切換任務。示例:
兩個同優先級任務?TaskA
?和?TaskB
?會交替運行(每 1 Tick 切換一次)。
Tick中斷函數的作用:
(1)取出下一個Task
(2)切換:
a.保存當前Task
b.恢復新的Task
(2) 延時與阻塞
vTaskDelay()
?的實現:
任務調用?vTaskDelay(ticks)
?后,會被移入阻塞列表,直到?xTickCount
?遞增到指定值。void vTaskDelay(TickType_t xTicksToDelay) {vTaskSuspendAll(); // 暫停調度器xTickCount += xTicksToDelay;prvAddCurrentTaskToDelayedList(xTicksToDelay);xTaskResumeAll(); // 恢復調度器 }
例如存在以下三個任務,其中任務1和任務2優先級相同,任務3優先級最高。
#include <stdio.h>/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"/* Library includes. */
#include "stm32f10x_it.h"extern void UART_Init(unsigned long ulWantedBaud);/* Demo app includes. */
static void prvSetupHardware( void );static volatile int flagIdleTaskrun = 0; // 空閑任務運行時flagIdleTaskrun=1
static volatile int flagTask1run = 0; // 任務1運行時flagTask1run=1
static volatile int flagTask2run = 0; // 任務2運行時flagTask2run=1
static volatile int flagTask3run = 0; // 任務3運行時flagTask3run=1/*-----------------------------------------------------------*/void vTask1( void *pvParameters )
{/* 任務函數的主體一般都是無限循環 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 1;flagTask2run = 0;flagTask3run = 0;/* 打印任務的信息 */printf("T1\r\n"); }
}void vTask2( void *pvParameters )
{ /* 任務函數的主體一般都是無限循環 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 1;flagTask3run = 0;/* 打印任務的信息 */printf("T2\r\n"); }
}void vTask3( void *pvParameters )
{ const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL ); /* 任務函數的主體一般都是無限循環 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 0;flagTask3run = 1;/* 打印任務的信息 */printf("T3\r\n"); // 如果不休眠的話, 其他任務無法得到執行vTaskDelay( xDelay5ms );}
}void vApplicationIdleHook(void)
{flagIdleTaskrun = 1;flagTask1run = 0;flagTask2run = 0;flagTask3run = 0; /* 故意加入打印讓flagIdleTaskrun變為1的時間維持長一點 *///printf("Id\r\n");
}int main( void )
{prvSetupHardware();xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);/* 啟動調度器 */vTaskStartScheduler();/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */return 0;
}
/*-----------------------------------------------------------*/static void prvSetupHardware( void )
{/* Start with the clocks in their e
如果在任務3中調用vTaskDelay(ticks)
,這里設定延遲5ms,運行后可以看到,任務1,2,3是交替運行的,如下圖所示。
如果不調用vTaskDelay(ticks)
,因為任務3的優先級最高,任務1和2將無法運行,如下所示:
注:若任務1,任務2和任務3的優先級相同,則這三個任務輪流執行,且是任務3先執行,然后是任務1,最后是任務2執行。?