1. 任务管理 (Task Management)
FreeRTOS 不存在进程概念,仅提供任务抽象。所有任务运行于单一地址空间内,依赖任务优先级与调度器完成 CPU 时间分配。
1.1 任务控制块 (TCB)
每个任务对应一个 TCB(Task Control Block),存储任务状态、优先级、栈指针、延时计数等全部元数据,是内核管理任务的核心数据结构。
1.2 任务状态
FreeRTOS 定义四种基本状态:
- 运行态 (Running):当前正占用 CPU 执行任务。
- 就绪态 (Ready):具备运行条件,等待调度器分配 CPU。
- 阻塞态 (Blocked):因调用延时函数(如
vTaskDelay())或等待同步/通信对象(如信号量、队列)而主动放弃 CPU,待条件满足后转入就绪态。 - 挂起态 (Suspended):被
vTaskSuspend()显式挂起,不参与调度,直至vTaskResume()恢复。
部分文档将 删除态 (Deleted) 列为第五种状态,指任务被 vTaskDelete() 删除后、TCB 与栈空间尚未回收的过渡阶段。
1.3 任务优先级
优先级数值越大,优先级越高。tskIDLE_PRIORITY(通常为 0)为最低优先级,空闲任务运行于该优先级。
1.4 空闲任务 (Idle Task)
调度器启动时自动创建,优先级最低。当所有用户任务均处于阻塞或挂起态时,CPU 执行空闲任务而非空转。可通过实现 vApplicationIdleHook() 注册空闲钩子,用于看门狗喂狗、低功耗模式切换等操作。空闲钩子内不可调用可能阻塞的 API。
1.5 任务的创建与删除
xTaskCreate() / xTaskCreateStatic() 用于创建任务,前者使用动态内存分配,后者由调用者提供静态 TCB 与栈空间。vTaskDelete() 删除任务,其 TCB 与栈的回收策略取决于内存管理方案配置(如 INCLUDE_vTaskDelete 宏)。
2. 调度器 (Scheduler)
调度器决定就绪任务中哪一个获得 CPU 使用权。
2.1 抢占式优先级调度
默认策略。始终选择当前就绪任务中优先级最高者运行。一旦高优先级任务就绪,低优先级任务立即被抢占挂起。
2.2 时间片轮转 (Time Slicing)
当多个就绪任务优先级相同时,调度器以 Tick 周期为单位轮流分配 CPU,每个任务执行一个时间片后切换。可通过 configUSE_TIME_SLICING 关闭。
2.3 协作式调度 (Co-operative)
任务仅在主动调用 taskYIELD() 或进入阻塞态时放弃 CPU,不支持强制抢占。现代工程中极少使用。
2.4 Tick 节拍
由硬件定时器产生周期性中断,频率由 configTICK_RATE_HZ 定义(通常 1000 Hz)。每次 Tick 中断中,调度器更新任务延时计数并检查是否触发任务切换。
3. 任务间通信 (IPC)
任务间不可直接通过全局变量共享数据而不加保护;若使用全局变量,必须以互斥量(Mutex)对访问操作加锁。FreeRTOS 提供以下专用通信机制。
3.1 队列 (Queue)
FIFO 缓冲区,用于任务间或中断与任务间传递结构化数据。创建时指定队列长度与单元素大小。xQueueSend() / xQueueReceive() 为基本操作接口。
队列内部已集成临界区保护,无需额外加锁。发送时若队列满,任务可选择阻塞等待;接收时若队列空,任务亦可阻塞等待,超时时间可配置。
3.2 任务通知 (Task Notification)
轻量级通信机制,每个任务自带一个 32 位通知值。性能开销远低于队列与信号量,但功能受限,仅支持一对一通知。
可作为二值信号量、计数信号量或携带 32 位数据值的通信方式使用。
4. 任务间同步 (Synchronization)
同步机制解决执行时序控制与资源独占问题。
4.1 二值信号量 (Binary Semaphore)
仅含空(0)与满(1)两种状态,本质为长度 1 且不存储实际数据的队列。典型应用场景:中断服务程序中调用 xSemaphoreGiveFromISR() 发信号,任务中调用 xSemaphoreTake() 阻塞等待,实现中断触发与任务处理的同步。
无优先级继承机制,不可用于保护共享资源。
4.2 计数信号量 (Counting Semaphore)
内部维护一个计数器,上限可配置。用于管理有限资源数量,例如缓冲池含 N 个缓冲区时,控制最多 N 个任务同时获取资源。
xSemaphoreGive() 使计数器 +1,xSemaphoreTake() 使计数器 -1;计数器为 0 时,后续获取请求将阻塞。
4.3 互斥量 (Mutex)
专用于互斥访问共享资源(如 I2C、SPI、UART 等外设)。与二值信号量外观相似,但存在本质差异:
- 所有权机制:仅执行
xSemaphoreTake()成功的任务可调用xSemaphoreGive()释放。 - 优先级继承:若高优先级任务因等待 Mutex 被低优先级任务阻塞,内核临时将低优先级任务的优先级提升至与高优先级任务同级,以抑制优先级翻转(Priority Inversion)。低优先级任务释放 Mutex 后,优先级恢复至原始值。
互斥量不可在中断中使用,中断上下文不支持阻塞,且优先级继承机制对中断无意义。
4.4 递归互斥量 (Recursive Mutex)
允许同一任务多次获取而不产生死锁,每次获取使嵌套计数 +1,必须同等次数释放才能完全解锁。适用于任务内部多层函数调用嵌套加锁的场景。
获取接口为 xSemaphoreTakeRecursive(),释放接口为 xSemaphoreGiveRecursive()。
4.5 事件组 (Event Group)
一个 32 位(或 24 位,取决于 configUSE_16_BIT_TICKS 配置)位图,每个位代表一个独立事件。任务可等待多个位同时满足(AND 逻辑)或任意一个位满足(OR 逻辑)。
适用于"等待传感器 A 就绪 且 传感器 B 就绪后启动采集"等多条件组合场景。事件组不可用于中断与任务间传递数据,仅用于事件同步。
5. 内存管理 (Memory Management)
FreeRTOS 提供多种堆分配策略,由 heap_x.c 实现:
- Heap_1:仅支持分配,不支持释放,无内存碎片,适用于任务从不删除的系统。
- Heap_2:最佳匹配算法,支持释放,但产生内存碎片,已不推荐。
- Heap_3:封装标准库
malloc()/free(),通过调度器锁保证线程安全,依赖编译器实现。 - Heap_4:首次匹配算法,支持释放并合并相邻空闲块,显著缓解碎片问题,工程中最常用。
- Heap_5:在 Heap_4 基础上支持跨多个不连续内存区域分配。
每个任务拥有独立栈空间,创建时指定大小。FreeRTOS 提供栈溢出检测钩子(vApplicationStackOverflowHook())用于调试。动态内存分配失败钩子(vApplicationMallocFailedHook())可于分配失败时触发告警。
6. 中断与临界区 (Interrupt & Critical Section)
6.1 临界区
临界区(Critical Section)指访问共享资源或执行不可中断操作时必须保护的代码段。FreeRTOS 通过 taskENTER_CRITICAL() / taskEXIT_CRITICAL() 实现临界区保护,其底层实现因移植层(Port Layer)而异:
- 在 Cortex-M 等架构中,通常通过提升
BASEPRI寄存器阈值屏蔽特定优先级以下的中断,或完全关闭中断(portDISABLE_INTERRUPTS)。 - 支持嵌套:多次调用
taskENTER_CRITICAL()需同等次数调用taskEXIT_CRITICAL()才能完全退出临界区。内核通过uxCriticalNesting计数器跟踪嵌套深度。
中断中的临界区:中断服务程序内使用 taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()(在 Cortex-M 中通常操作 BASEPRI),避免完全关闭中断导致高优先级中断丢失。
6.2 临界区与互斥量的区别
| 维度 | 临界区 | 互斥量 |
|---|---|---|
| 实现机制 | 关中断 / 提升中断屏蔽级 | 调度器层面的任务阻塞与唤醒 |
| 作用范围 | 全局(影响所有任务与中断) | 仅影响等待该 Mutex 的任务 |
| 阻塞可能性 | 临界区内不可阻塞 | 可阻塞等待 |
| 适用场景 | 极短代码段、寄存器操作 | 较长临界区、外设访问 |
| 时间开销 | 极小 | 涉及上下文切换,开销较大 |
临界区内严禁调用可能引发任务切换或阻塞的 API(如 vTaskDelay()、队列发送/接收等)。
6.3 中断安全 API
中断服务程序中不可调用普通 API(如 xQueueSend()),必须使用带 FromISR 后缀的版本(如 xQueueSendFromISR())。此类版本不会阻塞,且通过 pxHigherPriorityTaskWoken 参数标记是否需触发上下文切换,最终于中断退出前调用 portYIELD_FROM_ISR() 完成切换。
6.4 延迟中断处理
为降低中断延迟,ISR 中仅执行最短必要操作(如发送信号量或向队列写入数据),实际处理逻辑交由高优先级任务完成。
7. 时间管理 (Time Management)
7.1 相对延时
vTaskDelay() 从当前时刻起阻塞指定 Tick 数。由于任务执行时间存在抖动,实际周期会累积漂移。
7.2 绝对延时
vTaskDelayUntil() 基于上一次唤醒时刻计算下一次唤醒点,适用于固定周期任务(如每 10 ms 采样一次),可消除累积漂移。
7.3 软件定时器 (Software Timer)
由 Tick 回调驱动的定时器,分一次性(One-shot)与周期性(Auto-reload)两种。定时器回调运行于**定时器服务任务(Timer Service Task)**上下文,非中断上下文。定时器服务任务优先级由 configTIMER_TASK_PRIORITY 定义,其栈大小由 configTIMER_TASK_STACK_DEPTH 定义。
8. 内核对象与钩子 (Kernel Objects & Hooks)
8.1 内核对象
队列、信号量、互斥量、事件组等均通过句柄(Handle)引用,内核统一管理其生命周期。创建时通常返回 QueueHandle_t 或 SemaphoreHandle_t,实质均为指向内核对象控制块的指针。
8.2 常用钩子函数
- 空闲钩子:
vApplicationIdleHook(),在空闲任务循环中调用,需开启configUSE_IDLE_HOOK。 - Tick 钩子:
vApplicationTickHook(),在每次 Tick 中断中调用,需开启configUSE_TICK_HOOK。Tick 钩子内不可调用阻塞 API。 - 栈溢出钩子:
vApplicationStackOverflowHook(),检测到栈溢出时调用,需开启configCHECK_FOR_STACK_OVERFLOW。 - 内存分配失败钩子:
vApplicationMallocFailedHook(),动态内存分配失败时调用,需开启configUSE_MALLOC_FAILED_HOOK。
附录:机制选择速查
| 场景 | 推荐机制 |
|---|---|
| 任务间传递数据 | Queue |
| 轻量级通知 / 同步 | Task Notification |
| 中断同步任务 | Binary Semaphore |
| 保护共享资源(I2C / SPI / UART 等) | Mutex |
| 管理 N 个同类资源池 | Counting Semaphore |
| 多条件组合等待 | Event Group |
| 任务内嵌套锁 | Recursive Mutex |