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 等外设)。与二值信号量外观相似,但存在本质差异:

  1. 所有权机制:仅执行 xSemaphoreTake() 成功的任务可调用 xSemaphoreGive() 释放。
  2. 优先级继承:若高优先级任务因等待 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_tSemaphoreHandle_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