[
ModelEngine·创作计划征文活动
10w+人浏览
1.3k人参与
](https://activity.csdn.net/writing?id=11016)
https://www.freertos.org/zh-cn-cmn-s/Documentation/00-OverviewFreeRTOS 官方文档:https://www.freertos.org/zh-cn-cmn-s/Documentation/00-Overview
FreeRTOS 核心是多任务调度,任务管理则是对任务的「创建、调度、状态切换、删除」等全生命周期操作的管控,核心目标是让多个任务按优先级有序共享 CPU 资源,实现实时响应。
void taskfunction(void *argument)
{
for(;;)
{
printf("task runing!\n");
//延时 TICK_RATE_HZ ---1000
vTaskDelay(1000);
}
}
FreeRTOS 任务优先级是 任务调度的核心依据,直接决定任务抢占 CPU 的权限 —— 高优先级任务能打断低优先级任务执行,同优先级任务按时间片轮流执行。

#define configMAX_PRIORITIES ( 56 )
configMAX_PRIORITIES - 1(最高优先级);创建任务
xTaskCreate
xTaskCreateStatic
函数原型:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
函数功能:
动态创建任务(推荐,资源自动分配 / 释放)
参数说明:
TaskFunction_t pxTaskCode
类型:TaskFunction_t 是 FreeRTOS 定义的任务函数指针类型,原型为 void (*TaskFunction_t)(void *pvParameters);
const char * const pcName
类型:常量字符串指针(双重 const 表示字符串内容和指针本身都不可修改);
作用:任务名称,仅用于 调试和追踪(如通过调试工具查看任务列表),对任务运行无实际影响;
要求:长度建议不超过 configMAX_TASK_NAME_LEN(FreeRTOSConfig.h 中配置,默认 16 个字符);
const configSTACK_DEPTH_TYPE usStackDepth
类型:configSTACK_DEPTH_TYPE 是 FreeRTOS 定义的栈深度类型(本质是 uint16_t 或 uint32_t,由移植层决定);
作用:指定任务栈的大小(单位:StackType_t 元素个数,而非字节数);
可通过 configCHECK_FOR_STACK_OVERFLOW 启用栈溢出检测,避免栈太小导致崩溃。
void * const pvParameters
类型:void* 通用指针(可传递任意类型数据);
作用:创建任务时,向任务函数传递参数(任务函数的唯一参数);
无需传递参数:设为 NULL(最常用);
传递单个变量:直接取变量地址(需确保变量生命周期足够);
传递多个参数:用结构体封装,传递结构体地址;
UBaseType_t uxPriority
类型:UBaseType_t 是 FreeRTOS 定义的无符号基础类型(32 位架构下为 uint32_t);
作用:指定任务的优先级;
高优先级任务会抢占低优先级任务,同优先级任务按时间片轮流执行;
避免将过多任务设为最高优先级(会导致低优先级任务 “饿死”)。
TaskHandle_t * const pxCreatedTask
类型:TaskHandle_t(任务句柄类型)的指针,属于「输出参数」;
作用:任务创建成功后,FreeRTOS 会将该任务的句柄写入该指针指向的变量,后续可通过该句柄操作任务(如挂起、删除、修改优先级);
需后续操作任务:定义 TaskHandle_t 变量,传递其地址(如 &xLEDTaskHandle);
无需后续操作:设为 NULL(无需保存句柄)。
函数原型:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
函数功能:
动态创建的 xTaskCreate() 核心区别是:任务栈和任务控制块(TCB)的内存需用户手动分配(静态内存,如全局数组、静态数组),不依赖 FreeRTOS 动态堆。
参数说明:
TaskFunction_t pxTaskCode
类型:TaskFunction_t 是 FreeRTOS 定义的任务函数指针类型,原型为 void (*TaskFunction_t)(void *pvParameters);
const char * const pcName
类型:常量字符串指针(双重 const 表示字符串内容和指针本身都不可修改);
作用:任务名称,仅用于 调试和追踪(如通过调试工具查看任务列表),对任务运行无实际影响;
要求:长度建议不超过 configMAX_TASK_NAME_LEN(FreeRTOSConfig.h 中配置,默认 16 个字符);
const configSTACK_DEPTH_TYPE usStackDepth
类型:configSTACK_DEPTH_TYPE 是 FreeRTOS 定义的栈深度类型(本质是 uint16_t 或 uint32_t,由移植层决定);
作用:指定任务栈的大小(单位:StackType_t 元素个数,而非字节数);
可通过 configCHECK_FOR_STACK_OVERFLOW 启用栈溢出检测,避免栈太小导致崩溃。
void * const pvParameters
类型:void* 通用指针(可传递任意类型数据);
作用:创建任务时,向任务函数传递参数(任务函数的唯一参数);
无需传递参数:设为 NULL(最常用);
传递单个变量:直接取变量地址(需确保变量生命周期足够);
传递多个参数:用结构体封装,传递结构体地址;
UBaseType_t uxPriority
类型:UBaseType_t 是 FreeRTOS 定义的无符号基础类型(32 位架构下为 uint32_t);
作用:指定任务的优先级;
高优先级任务会抢占低优先级任务,同优先级任务按时间片轮流执行;
避免将过多任务设为最高优先级(会导致低优先级任务 “饿死”)。
StackType_t * const puxStackBuffer
类型:StackType_t(任务栈元素类型,32 位架构下 = uint32_t)的指针;
作用:指向用户提前静态分配的任务栈缓冲区(如全局数组、静态数组),FreeRTOS 会直接使用该缓冲区作为任务栈,不再从动态堆分配;
要求:
必须是静态内存(全局变量、static 变量),不能是局部变量(函数退出后内存销毁,任务运行崩溃)。
StaticTask_t * const pxTaskBuffer
类型:StaticTask_t(静态任务控制块类型)的指针;
作用:指向用户提前静态分配的任务控制块(TCB),TCB 用于存储任务的优先级、栈指针、状态等核心信息,FreeRTOS 不再从动态堆分配 TCB;
要求:
必须是静态内存(全局变量、static 变量),不能是局部变量。
函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )
函数功能:
从 RTOS 内核管理中移除任务。要删除的任务将从所有就绪、 阻塞、挂起和事件列表中移除。
参数说明:
TaskHandle_t xTaskToDelete
要删除的任务的句柄。如果传递 NULL,会删除调用任务。
注:空闲任务负责释放由 RTOS 内核分配给已删除任务的 内存。因此,如果应用程序调用了
vTaskDelete(),请务必确保空闲任务获得足够的微控制器处理时间。任务代码分配的内存不会自动释放, 应在任务删除之前手动释放。
函数原型
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
函数功能:
挂起任意任务。无论任务优先级如何,任务被挂起后将永远无法获取任何微控制器处理时间
对 vTaskSuspend的调用不会累积次数
参数说明:
TaskHandle_t xTaskToSuspend
被挂起的任务句柄。传递空句柄将导致调用任务被挂起。
函数原型
void vTaskResume( TaskHandle_t xTaskToResume )
函数功能:
恢复已挂起的任务,因一次或多次调用 vTaskSuspend() 而挂起的任务可通过单次调用
参数说明:
TaskHandle_t xTaskToResume
需要恢复挂起任务的句柄
函数原型
void vTaskDelay( const TickType_t xTicksToDelay )
函数功能:
调用该函数后,当前任务会放弃CPU使用权,进入阻塞状态,直到指定的tick数过去后才会重新进入就绪
状态,等待调度器再次调度。
注:vTaskDelay实现的是相对延时,即从调用函数的时刻开始计算延时时间
参数说明:
const TickType_t xTicksToDelay
表示阻塞的时钟节拍数(tick),而非实际时间(毫秒/秒)
延时时间 = 节拍数 × 系统节拍周期(configTICK_RATE_HZ 决定)
//#define configTICK_RATE_HZ ((TickType_t)1000)
函数原型
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement )
函数功能:
将任务延迟到指定时间。此函数可以由周期性任务使用, 来确保恒定的执行频率。
参数说明:
TickType_t * const pxPreviousWakeTime
类型:TickType_t(系统节拍计数类型,32 位架构下为 uint32_t)的指针;
作用:
输入:存储「上一次任务唤醒 / 执行的时刻」(第一次调用需初始化);
输出:函数内部自动更新为「本次任务唤醒的时刻」,供下次调用使用;
要求:
必须是任务内的静态变量(static)或全局变量(需保持生命周期,记录上一次时间);
第一次调用前必须初始化(用 xTaskGetTickCount() 获取当前系统节拍数)。
const TickType_t xTimeIncrement
类型:TickType_t,表示任务的「固定执行间隔」(单位:系统节拍 Tick);
换算:实际间隔时间 = xTimeIncrement × 节拍周期(默认 1Tick=1ms,xTimeIncrement=1000 对应 1 秒);
要求:间隔必须大于任务单次执行的耗时(否则任务会一直运行,无阻塞时间)。
vTaskDelay(x)(相对延时):从「函数调用时刻」开始计时,延时 x 个节拍后唤醒。若任务被高优先级任务抢占,实际执行间隔会变长(累计误差);
vTaskDelayUntil(&prev, x)(绝对延时):以「上一次唤醒时刻」为基准,计算下一次唤醒时刻(prev + x),确保任务执行间隔严格为 x 个节拍,无累计误差;
t0,调用后会阻塞到 t0 + 1000 时刻唤醒,即使被抢占,后续仍按固定间隔执行。| 对比维度 | vTaskDelayUntil(&xLastWakeTime, xPeriod) | vTaskDelay(xTicks) |
|---|---|---|
| 延时类型 | 绝对延时(基于上一次唤醒时刻) | 相对延时(基于当前调用时刻) |
| 累计误差 | 无(严格按固定周期执行) | 有(被抢占或业务耗时会导致间隔变长) |
| 适用场景 | 周期性任务(如数据采集、设备控制、定时刷新) | 非周期性延时(如 LED 闪烁、等待某个事件后延时) |
| 参数要求 | 需初始化 static 变量记录上一次唤醒时间 | 直接传入延时节拍数,无额外初始化 |
| 任务阻塞逻辑 | 阻塞到「上一次唤醒时刻 + 周期」 | 阻塞「当前时刻 + 延时」个节拍 |
FreeRTOS 任务有 5 种核心状态,调度器的核心工作就是触发状态切换,确保高优先级任务优先执行。
typedef enum
{
eRunning = 0, //任务自身查询自己的状态时,返回此值(仅当前正在占用 CPU 执行的任务会自报该状态)
eReady, //任务已就绪,等待 CPU 调度(处于「就绪列表」或「挂起就绪列表」,具备执行条件但暂未获得 CPU)
eBlocked, //任务阻塞状态(因等待资源、信号量、延时等主动放弃 CPU,需等待特定事件触发才能恢复就绪)
eSuspended, //任务挂起状态:1. 主动调用 vTaskSuspend() 挂起;2. 阻塞状态且超时时间为无限(portMAX_DELAY)
eDeleted, //任务已被删除(调用 vTaskDelete()),但对应的 TCB(任务控制块)尚未被系统回收释放
eInvalid //无效状态(用于错误处理)
} eTaskState;
| 状态 | 说明 | 触发条件示例 |
|---|---|---|
| 就绪态(Ready) | 任务已具备执行条件,等待调度器分配 CPU | 任务创建后、阻塞延时结束、被唤醒 |
| 运行态(Running) | 任务正在占用 CPU 执行代码(同一时刻,仅 1 个任务处于运行态) | 调度器选择就绪态中最高优先级任务 |
| 阻塞态(Blocked) | 任务暂时无法执行(如延时、等待队列 / 信号量),不占用 CPU 资源 | vTaskDelay()、xQueueReceive() |
| 挂起态(Suspended) | 任务被强制暂停,需通过特定 API 唤醒,不占用 CPU 资源 | vTaskSuspend() 调用 |
| 休眠态(Deleted) | 任务被删除,资源(栈、堆)被释放(仅动态创建的任务支持) | vTaskDelete() 调用 |

FreeRTOS 中的任务调度,本质是「操作系统内核(调度器)按预设规则,在多个任务间分配 CPU 执行权」的过程 —— 简单说就是 “决定哪个任务该占用 CPU 运行、哪个任务该暂停等待”,最终实现 “多任务并发执行” 的效果(实际是 CPU 快速切换任务,宏观上看起来多个任务同时运行)。
FreeRTOS中开启任务调度的函数是 vTaskStartScheduler() ,但在CubeMX中被封装为osKernelStart() 。
osStatus_t osKernelStart (void) {
osStatus_t stat;
if (IS_IRQ()) {
stat = osErrorISR;
}
else {
if (KernelState == osKernelReady) {
KernelState = osKernelRunning;
vTaskStartScheduler();
stat = osOK;
} else {
stat = osError;
}
}
return (stat);
}
FreeRTOS 调度器的核心是「抢占式调度 + 时间片调度」,确保实时性:
抢占式调度(默认启用):
时间片调度(默认启用):
协作式调度:
在FreeRTOS中,如果一个任务在时间片没有用完的情况下变为阻塞状态,它将释放时间片剩余时间,并且调度器会利用这部分时间来执行其他任务。具体来说,以下是处理这种情况的步骤:
通过这种方式,FreeRTOS确保了系统资源的有效利用,避免了因单个任务长时间占用CPU而造成的其他任务饥饿问题。这也体现了FreeRTOS的抢占式调度特性,即任何时刻,CPU总是由当前可运行的最高优先级任务占用。如果当前任务阻塞,调度器会立即抢占CPU,给其他就绪的任务一个运行的机会。
调度器的工作可概括为 “检查 → 选择 → 切换” 三步,在 SysTick中断或任务状态变化时触发:
FreeRTOS 中的上下文切换(Context Switch),是调度器实现多任务并发的核心技术 —— 本质是「保存当前运行任务的 “执行状态快照”,并恢复下一个要执行任务的 “快照”」的过程。通过快速切换,CPU 能在多个任务间切换执行,宏观上呈现 “多任务同时运行” 的效果。
简单说:上下文切换 = 保存当前任务状态 + 恢复目标任务状态,整个过程由 FreeRTOS 内核(移植层)自动完成,用户无需手动干预。
当切换触发时,CPU 首先**禁用 CPU 中断(避免切换过程被打断,确保原子性)**执行以下操作(汇编实现,速度极快);
| 数据类型 | 具体内容 | 存储位置 |
|---|---|---|
| 寄存器状态 | CPU 通用寄存器(R0~R15)、程序计数器(PC,记录下一条要执行的指令地址)、状态寄存器(PSR,记录 CPU 状态)等 | 任务专属栈空间 |
| 任务控制信息 | 栈指针(SP,指向任务栈当前位置)、任务优先级、任务状态(就绪 / 阻塞等) | 任务控制块(TCB) |

栈空间是任务上下文的 “存储容器”,TCB 是任务状态的 “管理中枢”,汇编代码负责高效操作 CPU 寄存器,确保切换速度。
vTaskDelay() 进入阻塞态;xQueueReceive()/xSemaphoreTake() 等待资源(队列 / 信号量为空),进入阻塞态;taskYIELD() 主动请求切换(让同优先级其他任务执行);FreeRTOS 中任务(上下文)切换的核心执行逻辑,正是在 PendSV 中断的服务函数(PendSV_Handler)中实现的——PendSV 中断被 FreeRTOS 专门设计为 “任务切换中断”,其核心价值是「确保任务切换在合适的时机(低优先级中断上下文)执行,不影响高优先级中断响应」。
PendSV(Pendable Service Call,可挂起的系统调用)是 Cortex-M 架构内核自带的低优先级系统中断,FreeRTOS 选中它做任务切换的核心原因的是其独特的 “可挂起 + 低优先级” 特性,完美匹配任务切换的需求。
vTaskSwitchContext()),PendSV 中断的核心是 “执行切换动作”(保存 / 恢复上下文);PendSV_Handler 是汇编实现的,直接操作 CPU 寄存器和栈指针,确保切换效率(几微秒级);PendSV_Handler 中完成实际切换。当需要任务切换触发后,调度器会调用 vPortYield()(或 portYIELD_FROM_ISR()),通过软件置位 PENDSVSET 位,发起 PendSV 中断请求。
PendSV_Handler)当没有更高优先级中断执行时,CPU 响应 PendSV 中断,进入 PendSV_Handler 服务函数 —— 这是任务切换的 “执行入口”。
关键细节:
PendSV_Handler 是汇编函数(位于移植层 portasm.s),而非 C 函数,原因是需要直接操作 CPU 寄存器(保存 / 恢复上下文),汇编执行效率更高;PendSV_Handler 中完成上下文切换PendSV_Handler 的核心工作就是「完整保存当前任务上下文 + 恢复目标任务上下文」
具体汇编逻辑(以 Cortex-M3 为例):
PendSV_Handler:
; 1. 保存当前任务的上下文(补充保存 CPU 未自动压栈的寄存器)
MRS R0, PSP ; 读取当前任务的栈指针(PSP,任务模式栈指针)
STMFD R0!, {R4-R11} ; 将 R4~R11 寄存器压入当前任务栈(CPU 未自动压栈)
LDR R1, =pxCurrentTCB ; 读取当前任务的 TCB 指针
STR R0, [R1] ; 更新 TCB 中的栈指针(保存当前栈位置)
; 2. 调用调度器函数 vTaskSwitchContext(),选择下一个要执行的任务(C 函数)
PUSH {LR} ; 保存返回地址(LR)
BL vTaskSwitchContext ; 调度器选择目标任务,更新 pxCurrentTCB 为目标任务 TCB
POP {LR} ; 恢复 LR
; 3. 恢复目标任务的上下文
LDR R0, =pxCurrentTCB ; 读取目标任务的 TCB 指针
LDR R0, [R0] ; 读取目标任务的栈指针
LDMFD R0!, {R4-R11} ; 从目标任务栈中弹出 R4~R11 寄存器
MSR PSP, R0 ; 恢复目标任务的栈指针(PSP)
; 4. 退出 PendSV 中断,恢复 CPU 自动压栈的寄存器,执行目标任务
BX LR ; 退出中断,PC 指向目标任务的下一条指令
无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力