FreeRTOS 基础入门:从任务调度到内核对象
1. 什么是 FreeRTOS
FreeRTOS 是一个开源、轻量级的实时操作系统(RTOS)内核,由 Richard Barry 主导开发,后被 Amazon 托管并更名为 Amazon FreeRTOS 的一部分。它采用 C 语言编写,支持从 8 位单片机到 32 位高端 MCU 的多种平台,凭借源码免费、文档齐全、生态成熟等优势,成为嵌入式领域使用最广泛的 RTOS 之一。
与裸机程序(前后台系统)不同,FreeRTOS 提供了一套完整的多任务调度机制,让开发者可以把复杂应用拆分成多个相对独立的任务,由内核统一管理 CPU 时间。理解 FreeRTOS 的基础知识,是进入实时嵌入式开发的重要一步。
2. 为什么需要 RTOS
在传统的裸机开发中,程序通常由一个超级循环和若干中断服务函数组成:
int main(void)
{
/* 硬件初始化 */
while (1)
{
task_led();
task_key();
task_uart();
task_sensor();
}
}
这种前后台结构简单直接,但随着业务复杂化会逐渐暴露出问题:
- 各个功能模块的执行时间相互干扰,实时性难以保证;
- 某个模块阻塞(如等待外设响应)会拖慢整个主循环;
- 业务耦合严重,代码结构随功能增加而迅速恶化。
RTOS 的价值在于把「谁来执行、何时执行」交给调度器统一处理。每个任务只专注自己的逻辑,由内核基于优先级和时间片切换任务,既提高了实时响应能力,也让模块之间的边界更清晰。
3. 核心概念:任务与调度器
3.1 任务
任务是 FreeRTOS 中最基本的执行单元,本质是一个永不返回的 C 函数。每个任务拥有独立的栈空间,用于保存局部变量、函数调用现场等。
void vLedTask(void *pvParameters)
{
for (;;)
{
LED_Toggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
任务函数必须遵循「死循环 + 阻塞」的模式:要么在循环中主动让出 CPU,要么调用阻塞型 API(如 vTaskDelay),否则低优先级任务将永远得不到执行机会。
3.2 调度器
调度器是 FreeRTOS 的心脏,负责在任务就绪时决定下一个运行的任务。FreeRTOS 默认使用优先级抢占式调度:
- 数值越大,任务优先级越高;
- 高优先级任务就绪后会立即抢占低优先级任务;
- 同优先级任务可以通过时间片轮流执行。
调度器由 vTaskStartScheduler() 启动,启动后通常不再返回:
int main(void)
{
xTaskCreate(vLedTask, "led", 128, NULL, 1, NULL);
xTaskCreate(vUartTask, "uart", 512, NULL, 2, NULL);
vTaskStartScheduler();
/* 正常情况不会执行到这里 */
for (;;);
}
3.3 任务状态
FreeRTOS 中的任务在生命周期内会在以下几种状态间迁移:
- 运行态:当前正在占用 CPU 的任务,单核系统中同一时刻只有一个;
- 就绪态:具备执行条件,等待调度器分配 CPU;
- 阻塞态:等待某个事件而暂时不能执行,如延时、等信号量、等队列数据;
- 挂起态:被
vTaskSuspend()挂起,不参与调度,直到被vTaskResume()恢复。
4. 任务创建与常用 API
创建任务使用 xTaskCreate():
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
const char * const pcName,
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask);
参数含义如下:
pxTaskCode:任务函数指针;pcName:任务名称,便于调试;usStackDepth:栈深度,单位是字(word)而不是字节;pvParameters:传给任务函数的参数;uxPriority:任务优先级;pxCreatedTask:任务句柄,可用于后续删除、挂起、恢复该任务。
常用的任务控制接口包括:
| 函数 | 作用 |
|---|---|
vTaskDelay() | 相对延时,阻塞指定 tick |
vTaskDelayUntil() | 绝对延时,保证周期性执行 |
vTaskSuspend() | 挂起任务 |
vTaskResume() | 恢复挂起的任务 |
vTaskDelete() | 删除任务 |
vTaskPrioritySet() | 动态修改任务优先级 |
5. 常用内核对象
任务之间需要通信与同步,FreeRTOS 提供了丰富的内核对象。
5.1 队列
队列用于任务间传递数据,是 FreeRTOS 中最基础的通信机制。数据以拷贝的方式入队,发送方和接收方不共享内存。
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));
int tx_value = 42;
xQueueSend(xQueue, &tx_value, portMAX_DELAY);
int rx_value = 0;
xQueueReceive(xQueue, &rx_value, portMAX_DELAY);
5.2 信号量
信号量用于资源计数与同步,常见类型有:
- 二值信号量:取值 0 或 1,常用于任务与中断之间的同步;
- 计数信号量:可累计计数值,适合资源池管理;
- 互斥量(Mutex):特殊的二值信号量,带优先级继承机制,专门用于保护共享资源,避免优先级翻转。
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
{
/* 访问临界资源 */
xSemaphoreGive(xMutex);
}
5.3 事件组
事件组允许一个任务等待多个事件位同时满足,适合「多条件汇合」的场景:
EventGroupHandle_t xEvents = xEventGroupCreate();
/* 等待 bit0 和 bit1 同时置位 */
xEventGroupWaitBits(xEvents,
BIT0 | BIT1,
pdTRUE, /* 等待后清除事件位 */
pdTRUE, /* 全部满足才返回 */
portMAX_DELAY);
5.4 软件定时器
FreeRTOS 的软件定时器由内核服务任务统一管理,回调函数运行在定时器服务任务的上下文中,因此回调内不能调用会阻塞的 API:
TimerHandle_t xTimer = xTimerCreate("timer",
pdMS_TO_TICKS(1000),
pdTRUE, /* 自动重载 */
(void *)0,
vTimerCallback);
xTimerStart(xTimer, 0);
6. 中断与临界区
FreeRTOS 通过「可管理的临界区」来保护共享资源。根据使用场景的不同,可以选择不同的保护手段:
- 临界区宏:
taskENTER_CRITICAL()与taskEXIT_CRITICAL(),通过关闭中断保护极短的代码段,但会延迟中断响应,不宜长时间使用; - 挂起调度器:
vTaskSuspendAll()与xTaskResumeAll(),防止任务切换,但中断仍可响应; - 互斥量:适合保护访问时间较长的共享资源。
对于中断服务函数,FreeRTOS 提供专门的 ...FromISR() 系列 API 用于发送队列或信号量,例如 xQueueSendFromISR()、xSemaphoreGiveFromISR(),其末尾需要手动判断是否触发一次任务切换:
void UART_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint8_t ch = UART_ReadByte();
xQueueSendFromISR(xUartQueue, &ch, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
7. 内存管理
FreeRTOS 提供了多种内存分配方案(heap_1 到 heap_5),开发时需要在 FreeRTOSConfig.h 中选择其一:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| heap_1.c | 只分配不释放 | 创建后不会删除对象 |
| heap_2.c | 支持释放,无碎片合并 | 旧版本常用 |
| heap_3.c | 包装标准 malloc/free | 依赖系统 C 库 |
| heap_4.c | 支持释放与相邻块合并 | 最常用的推荐方案 |
| heap_5.c | heap_4 基础上支持多段内存 | 内存分布不连续时 |
所有内核对象(任务、队列、信号量等)的内存都由所选 heap 方案分配,因此合理配置 configTOTAL_HEAP_SIZE 对系统稳定运行至关重要。
8. 关键配置项
FreeRTOS 的行为高度依赖 FreeRTOSConfig.h 中的配置,初学者需要重点关注以下几项:
#define configUSE_PREEMPTION 1 /* 使用抢占式调度 */
#define configUSE_TIME_SLICING 1 /* 同优先级时间片轮转 */
#define configTICK_RATE_HZ 1000 /* 系统节拍频率 */
#define configMAX_PRIORITIES 8 /* 最大优先级数 */
#define configMINIMAL_STACK_SIZE 128 /* 空闲任务最小栈深 */
#define configTOTAL_HEAP_SIZE (8 * 1024) /* 内核可分配内存 */
其中 configTICK_RATE_HZ 决定了时间基准,常见取值为 1000,即每 1ms 产生一次系统节拍中断。所有基于时间的功能(如 vTaskDelay)都与它密切相关。
9. 一个完整的入门示例
下面是一个基于 FreeRTOS 的两任务示例,演示了任务创建与队列通信:
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
static QueueHandle_t xDataQueue;
void vProducerTask(void *pvParameters)
{
int count = 0;
for (;;)
{
xQueueSend(xDataQueue, &count, portMAX_DELAY);
count++;
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vConsumerTask(void *pvParameters)
{
int value = 0;
for (;;)
{
if (xQueueReceive(xDataQueue, &value, portMAX_DELAY) == pdTRUE)
{
/* 处理接收到的数据 */
(void)value;
}
}
}
int main(void)
{
xDataQueue = xQueueCreate(8, sizeof(int));
xTaskCreate(vProducerTask, "producer", 256, NULL, 1, NULL);
xTaskCreate(vConsumerTask, "consumer", 256, NULL, 2, NULL);
vTaskStartScheduler();
for (;;);
return 0;
}
10. 总结
FreeRTOS 的学习可以围绕一个主线展开:任务如何创建与调度,任务之间如何通信与同步,共享资源如何保护。掌握了任务、队列、信号量、互斥量这几个核心概念后,就能应对大多数常规的嵌入式应用需求。
初学时建议先基于官方移植好的开发板例程跑通多任务,再逐步深入源码理解调度器的实现,最后结合实际项目体会优先级设置、栈大小配置和内存分配对系统行为的影响。扎实掌握这些基础,是后续学习更复杂 RTOS 特性(如流缓冲区、消息缓冲区、协程等)的前提。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)