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):#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执行。

3439

被折叠的 条评论
为什么被折叠?



