FreeRTOS:任务(Tasks)与调度基础
把当前任务从 Ready → Blocked,至少阻塞这么多个 tick。⚠️ 实时系统中不建议频繁创建 / 删除任务(碎片 + 不确定性)。如果在 host 模拟环境里看到 main 返回,各位可以在自己的上位机环境中试一下 😃。这就是后面互斥锁必须存在的根本原因。这就是 RTOS 和裸机。在“就绪态”的任务中,选。
·
FreeRTOS:任务(Tasks)与调度基础
这里把上位机RTOS的坑填上:
FreeRTOS 中的“任务”究竟是什么
一句话定义:
任务 = 一段永不返回的 C 函数 + 一块独立的栈 + 一份内核管理结构(TCB)
在 FreeRTOS 里:
-
每个任务都有自己的栈(不是共用)
-
每个任务都有一个 TCB(Task Control Block)
-
调度器只做一件事:
在“就绪态”的任务中,选优先级最高的那个运行
你可以把它理解成:
while (system_running) {
选出最高优先级的 Ready 任务
切换上下文
让它跑一会儿
}
任务 ≠ 函数调用
- 函数:调用 → 返回
- 任务:创建 → 运行 → 阻塞 / 就绪 → 再运行(通常永不返回)
这就是 RTOS 和裸机 while(1) 的本质差异。
2. xTaskCreate —— 创建任务
2.1 API 原型
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pvCreatedTask
);
2.2 每个参数的含义
| 参数 | 含义 |
|---|---|
pvTaskCode |
任务入口函数(不能返回) |
pcName |
任务名字,仅用于调试 |
usStackDepth |
任务私有栈大小 |
pvParameters |
传给任务的参数 |
uxPriority |
任务优先级(数值越大越高) |
pvCreatedTask |
输出任务句柄(可为 NULL) |
- 需要注意的是——
xTaskCreate只负责创建 - 任务真正开始跑,是在
vTaskStartScheduler()之后
3. vTaskStartScheduler —— 调度器启动
void vTaskStartScheduler(void);
这是一道分水岭 API。
- 调用之前:
- 你在“普通 C 世界”
- 只是注册了一堆任务
- 调用之后:
- CPU 控制权交给调度器
- main 函数理论上不再返回
int main(void)
{
xTaskCreate(...);
xTaskCreate(...);
vTaskStartScheduler();
// 走到这里 = 调度器启动失败
for (;;);
}
如果在 host 模拟环境里看到 main 返回,99% 是内存或配置问题。
4. vTaskDelay —— 相对延时
void vTaskDelay(TickType_t xTicksToDelay);
把当前任务从 Ready → Blocked,至少阻塞这么多个 tick
vTaskDelay(pdMS_TO_TICKS(500));
特点
- 延时是相对的
- 会随着代码执行时间产生时间漂移
适合:
- 日志打印
- 非严格周期任务
5. vTaskDelayUntil —— 绝对时间延时(周期任务)
void vTaskDelayUntil(
TickType_t *pxPreviousWakeTime,
TickType_t xTimeIncrement
);
void PeriodicTask(void *arg)
{
TickType_t last = xTaskGetTickCount();
for (;;)
{
printf("tick = %lu\n", xTaskGetTickCount());
vTaskDelayUntil(&last, pdMS_TO_TICKS(1000));
}
}
和 vTaskDelay 的根本区别
| API | 是否漂移 | 适用场景 |
|---|---|---|
| vTaskDelay | 会 | 普通等待 |
| vTaskDelayUntil | 不会 | 严格周期任务 |
这是实时系统里非常重要的一对 API。
6. vTaskDelete —— 删除任务
void vTaskDelete(TaskHandle_t xTaskToDelete);
- 删除指定任务
- 传
NULL:删除自己
vTaskDelete(NULL);
常见用途:
- 初始化任务跑完就退出
- 异常检测任务自杀
⚠️ 实时系统中不建议频繁创建 / 删除任务(碎片 + 不确定性)。
7. 任务栈 & TCB —— 为什么你必须关心它
每个任务都有:
- 私有栈(函数调用、局部变量、中断上下文)
- TCB(状态、优先级、栈指针等)
7.1 栈用多了会怎样?
- 覆盖别的内存
- 行为诡异
- 调试地狱
7.2 查看栈使用:uxTaskGetStackHighWaterMark
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);
UBaseType_t water = uxTaskGetStackHighWaterMark(NULL);
printf("stack left = %u\n", water);
含义:
历史最小剩余栈空间
- 越小说明越危险
- 接近 0 必炸
8. 优先级与调度规则
8.1 基本规则
- 优先级高的任务一定先跑
- 高优先级 Ready → 立刻抢占
- 同优先级 → 时间片轮转(若开启)
8.2 一个非常重要的现象:优先级反转
现象描述(先不解决):
- 低优先级任务 L 占着资源
- 高优先级任务 H 等这个资源,被阻塞
- 中优先级任务 M 疯狂运行
结果:
H 被 M 间接“压制”
这就是后面互斥锁必须存在的根本原因。
9. 实验 / 作业代码(完整)
9.1 实验目标
- 3 个任务
- 3 个优先级
- 用打印解释调度顺序
9.2 作业代码
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
void TaskHigh(void *arg)
{
for (;;)
{
printf("[H] run\n");
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void TaskMid(void *arg)
{
for (;;)
{
printf("[M] run\n");
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void TaskLow(void *arg)
{
for (;;)
{
printf("[L] run, stack=%u\n",
(unsigned)uxTaskGetStackHighWaterMark(NULL));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
xTaskCreate(TaskHigh, "H", 256, NULL, tskIDLE_PRIORITY + 3, NULL);
xTaskCreate(TaskMid, "M", 256, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(TaskLow, "L", 256, NULL, tskIDLE_PRIORITY + 1, NULL);
vTaskStartScheduler();
for (;;);
}
各位可以在自己的上位机环境中试一下 😃
更多推荐



所有评论(0)