FreeRTOS基础篇(二)——FreeRTOS工程介绍

前言:
   上一篇文章介绍了 FreeRTOS 的概念和核心功能,这一篇从源码工程的角度出发,介绍 FreeRTOS 的仓库组成、目录结构、内核文件、移植层和配置文件,帮助读者建立一个完整的工程视图。具体的移植操作放在进阶篇。

1 获取源码

  FreeRTOS 官网 支持中文,提供丰富的包括文档、教程和 API 参考等。
  可以从官网下载源码,也可以从 GitHub 仓库 clone 源码。FreeRTOS 在 GitHub 上按用途拆成了几个仓库,常见的有内核版、完整版、长期支持版和实验版:

仓库 定位 包含内容 更新策略 适用场景 是否适合生产环境
FreeRTOS-Kernel 内核版 仅内核源码和移植层(portable),不含协议栈、文件系统等中间件和 Demo 持续更新并打版本标签(如 V11.3.1),新功能先合入这里 只需要 RTOS 内核、其他组件自选的项目;它也是完整版和 LTS 版的子模块 是
FreeRTOS 完整版(Classic) 内核 + FreeRTOS-Plus 组件(TCP/IP、FAT、POSIX 等)+ 大量官方 Demo 跟随内核/LTS 更新,正式发行版更新较慢 学习、评估、参考官方 Demo 是
FreeRTOS-LTS 长期支持版 内核 + 常用 IoT 库的固定版本组合 只修复安全漏洞和关键 Bug,不新增功能;每个版本支持 2 年 工业级稳定产品、量产项目 是
FreeRTOS-Labs 实验版 实验性的库和 Demo(如 FAT、POSIX) 可能重构或废弃,不保证稳定 评估、学习、Demo 开发 否

  内核版(FreeRTOS-Kernel) 是 FreeRTOS 的最小集合,只包含任务调度、队列、信号量、软件定时器、事件组等内核功能以及各芯片架构的移植层。如果项目只需要一个 RTOS 内核,协议栈、文件系统等自己选型,直接使用这个仓库最干净;完整版和 LTS 版也是通过 Git 子模块的方式引用它。

  完整版(FreeRTOS) 是传统发行版,在内核之外还包含 FreeRTOS-Plus 组件和大量官方 Demo,适合学习和参考。它把内核等组件作为子模块引用,所以 clone 时要带 --recurse-submodules,否则子目录会为空。

  长期支持版(FreeRTOS-LTS) 由 AWS 维护,按 YYYYMM-LTS 分支维护,并用 YYYYMM.XX-LTS 标签发布补丁(如 202604.00-LTS、202604.01-LTS)。每个版本锁定一组经过验证的内核和 IoT 库版本,只提供安全更新和关键 Bug 修复,支持周期为 2 年。截至 2026 年 10 月,最新分支为 202604-LTS,最新补丁标签为 202604.01-LTS(内核 V11.3.1),支持到 2028-04-30;202406-LTS 已于 2026-06-30 结束支持。量产项目还可以购买 AWS 的 EMP(Extended Maintenance Plan),最多再延长 10 年维护。

  实验版(FreeRTOS-Labs) 中的库和 Demo 虽然可以使用,但可能随时重构或废弃,只适合评估和学习,不要用于生产。

  学习为目的建议使用完整版或内核版,能接触到最新的功能;工业开发则建议使用长期支持版,版本组合固定、维护周期明确。本系列以 FreeRTOS-LTS 为例,后续的目录和源码分析都基于 202604.01-LTS。

  官网也提供 下载链接 ,但更推荐从 GitHub 获取,方便切换版本和升级。这里以 LTS 版为例,执行以下指令将仓库克隆到本地(--recurse-submodules 会同时拉取子模块):

1
2
3
4
git clone --recurse-submodules https://github.com/FreeRTOS/FreeRTOS-LTS.git
cd FreeRTOS-LTS
git checkout 202604.01-LTS
git submodule update --init --recursive

  如果 clone 时没有加 --recurse-submodules,或者切换了分支/标签,都需要执行 git submodule update --init --recursive,否则组件目录可能为空或版本不匹配。可以用 git submodule status 查看各子模块当前指向的提交。

  切换版本时可以选择 202604-LTS 分支跟踪最新补丁,也可以 checkout 某个 YYYYMM.XX-LTS 标签锁定到具体版本。产品开发更推荐锁定标签,避免构建结果随分支更新而变化。

branch.png

2 FreeRTOS 工程结构

  FreeRTOS-LTS 是一个“集成仓库”(hub repo):它本身主要保存清单和子模块引用,真正的内核和组件代码分布在各个子模块仓库中。因此看目录结构时,要同时关注“仓库这一层”和“组件这一层”。理解了这两层,就能快速定位到需要的源码。

2.1 仓库根目录

  202604.01-LTS 的仓库根目录如下(省略了部分 GitHub 配置和文档文件):

1
2
3
4
5
6
7
8
9
FreeRTOS-LTS/
├── .gitmodules # 子模块配置,记录各组件仓库的地址和路径
├── CHANGELOG.md # 版本变更记录
├── FreeRTOS/ # FreeRTOS 内核和通用中间件
├── LICENSE.md # MIT 许可证
├── README.md # LTS 组件清单、版本和支持周期
├── aws/ # AWS IoT 相关库
├── manifest.yml # 当前版本锁定的组件及版本号
└── ...
目录/文件 说明
FreeRTOS/ 内核和通用中间件,是本文关注的重点
aws/ AWS IoT 设备影子、Jobs、Defender、Fleet Provisioning、SigV4、MQTT File Streams 等,只有接入 AWS IoT 时才需要
manifest.yml 清单文件,记录每个子模块锁定的版本,是判断“这个 LTS 版本包含什么”的最直接依据
.gitmodules Git 子模块配置,记录各组件仓库地址
README.md / CHANGELOG.md 发布说明和变更记录
LICENSE.md MIT 许可证

  LTS 的价值就在于 manifest.yml 里这一组固定的版本组合:它们是经过验证、可以一起工作的组件。相比自己分别拉取内核和各个中间件的最新代码,使用 LTS 可以显著减少版本兼容问题。

2.2 FreeRTOS 目录:内核与中间件

  FreeRTOS/ 目录下是内核和通用组件,目录结构如下:

1
2
3
4
5
6
7
8
9
10
FreeRTOS/
├── FreeRTOS-Kernel/ # 内核(必需)
├── FreeRTOS-Plus-TCP/ # TCP/IP 协议栈
├── FreeRTOS-Cellular-Interface/ # 蜂窝网络抽象层
├── backoffAlgorithm/ # 网络重试的指数退避算法
├── coreHTTP/ # HTTP 客户端
├── coreJSON/ # 轻量 JSON 解析器
├── coreMQTT/ # MQTT 客户端
├── corePKCS11/ # PKCS#11 加密接口
└── coreSNTP/ # SNTP 时间同步
组件 功能 是否必需
FreeRTOS-Kernel 任务调度、队列、信号量、事件组、软件定时器等内核功能 必需
FreeRTOS-Plus-TCP 完整的 TCP/IP 协议栈 可选
coreMQTT MQTT 客户端 可选
coreHTTP HTTP 客户端 可选
coreJSON 轻量 JSON 解析器 可选
coreSNTP SNTP 时间同步 可选
corePKCS11 PKCS#11 加密接口 可选
FreeRTOS-Cellular-Interface 蜂窝模组抽象层 可选
backoffAlgorithm 网络连接失败后的重试策略 可选

  FreeRTOS 采用模块化设计,以上组件不需要全部使用,只选择项目实际需要的部分即可。这样既能控制代码体积,也能减少不必要的依赖。需要注意,coreHTTP、coreMQTT 等名称中的“core”是 AWS 对这些通用库的命名,并不是内核核心的意思;真正必需的内核只有 FreeRTOS-Kernel。

  本文只展开内核部分。协议栈、MQTT、HTTP 等组件各自有独立的仓库和文档,用法会在需要时单独介绍。

2.3 FreeRTOS-Kernel 目录

  FreeRTOS-Kernel 是内核仓库。202604.01-LTS 锁定的内核版本为 V11.3.1,其目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FreeRTOS-Kernel/
├── croutine.c # 协程(历史特性,现代项目基本不用)
├── event_groups.c # 事件组
├── list.c # 内核链表
├── queue.c # 队列、信号量、互斥量的基础
├── stream_buffer.c # 流缓冲区和消息缓冲区
├── tasks.c # 任务管理和调度核心
├── timers.c # 软件定时器
├── include/ # 对外头文件
├── portable/ # 编译器、架构和内存管理移植层
├── examples/ # 最小示例和 FreeRTOSConfig.h 模板
├── CMakeLists.txt # CMake 构建入口
├── History.txt # 内核变更历史
├── MISRA.md # MISRA 合规说明
├── README.md
└── ...
文件/目录 作用
tasks.c 任务控制块、任务创建/删除/挂起/恢复、优先级管理和调度器核心
list.c 内核双向链表,就绪、阻塞、挂起等列表的基础
queue.c 队列实现;信号量和互斥量也建立在队列机制之上
stream_buffer.c 流缓冲区和消息缓冲区,适合字节流和变长消息
event_groups.c 事件组,用于多个事件的同步
timers.c 软件定时器服务任务和定时器管理
croutine.c 协程支持,属于历史特性,新项目基本不使用
include/ 内核对外头文件
portable/ 编译器、处理器架构和内存管理的移植层
examples/ 最小 CMake 示例和 FreeRTOSConfig.h 模板
CMakeLists.txt CMake 构建入口;也可以自行加入 Keil、IAR 或 Makefile 工程
History.txt / MISRA.md 变更历史和 MISRA 合规说明

  这几个 .c 文件并不是必须全部编译。tasks.c 和 list.c 是调度核心;queue.c 在使用队列、信号量或互斥量时必需;timers.c、event_groups.c、stream_buffer.c 可以按需裁剪;croutine.c 基本不用。具体裁剪由 FreeRTOSConfig.h 中的 configUSE_* 宏控制,后文会介绍。

  include/ 目录存放内核对外的头文件,常用的几个如下:

头文件 作用
FreeRTOS.h 内核主头文件,负责引入 FreeRTOSConfig.h、定义基础类型和默认配置;使用其他内核 API 前应先包含它
task.h 任务创建、删除、延时、优先级等 API
queue.h 队列 API
semphr.h 信号量、互斥量的宏封装(底层仍是队列)
event_groups.h 事件组 API
timers.h 软件定时器 API
stream_buffer.h / message_buffer.h 流缓冲区、消息缓冲区 API
portable.h 移植层对外接口(内核内部使用)
list.h / projdefs.h 内核链表和公共定义(内部使用)
mpu_wrappers.h / mpu_prototypes.h MPU 端口的系统调用封装
stack_macros.h / StackMacros.h 栈溢出检查相关宏

  examples/ 目录里有官方提供的最小示例和配置模板。examples/template_configuration/FreeRTOSConfig.h 是应用配置的起点,examples/cmake_example 则演示了如何用 CMake 组织一个最小内核工程。CMakeLists.txt 只是官方提供的一种构建方式,FreeRTOS 本身并不绑定构建系统,内核源码可以加入任何支持 C 的工程。

2.4 portable 移植层

  portable/ 是 FreeRTOS 可移植性的核心,存放针对不同编译器、处理器架构和内存管理方案的适配代码。目录很多,可以按类别理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
portable/
├── GCC/ # GNU 工具链
├── IAR/ # IAR Embedded Workbench
├── RVDS/ # ARM Compiler 5 / Keil MDK 常用端口
├── CCS/ # TI Code Composer Studio
├── MPLAB/ # Microchip MPLAB X
├── MSVC-MingW/ # Windows 下的仿真端口
├── MemMang/ # 内存管理方案 heap_1 ~ heap_5
├── Common/ # 公共移植代码(如 MPU 封装)
├── ARMv8M/ # Cortex-M23/M33 等带 TrustZone 的架构
├── template/ # 新 port 的参考模板
├── ThirdParty/ # 第三方维护的 port
└── ... # 其他编译器和架构
类别 目录/文件 功能描述
编译器支持 GCC/、IAR/、RVDS/、CCS/、MPLAB/ 不同编译器工具链的适配
处理器架构 GCC/ARM_CM3、IAR/ARM_CM4F、ARMv8M/ 等 具体内核的 port.c 和 portmacro.h
内存管理 MemMang/ 5 种动态内存分配策略
公共代码 Common/ MPU 封装等公共移植代码
模板 template/ 新端口开发的参考模板
第三方 ThirdParty/ 第三方移植和维护的代码
其他 CCRH/、MikroC/、SDCC/、Tasking/ 等 小众编译器或专用架构

  port 的路径通常按“先编译器、后架构”的方式组织,例如 GCC + Cortex-M3 对应 portable/GCC/ARM_CM3/,里面只有两个关键文件:

  • port.c:任务切换、中断入口、临界区、栈初始化等与硬件紧密相关的实现。
  • portmacro.h:端口相关的类型、宏和内联函数,例如 StackType_t、portSTACK_TYPE、临界区进出宏等。

  选择 port 时,先确定编译器和芯片内核,再考虑 FPU、MPU、TrustZone 等特性。下面是以 GCC 端口为例的常见对应关系:

处理器内核 GCC 端口示例 说明
Cortex-M0 / M0+ ARM_CM0 无 FPU
Cortex-M3 ARM_CM3 无 FPU
Cortex-M4F ARM_CM4F 带 FPU,需要保存浮点上下文
Cortex-M7 ARM_CM7 带 FPU
Cortex-M23 / M33 ARM_CM23 / ARM_CM33 TrustZone 相关端口可参考 ARMv8M/
带 MPU 的 M3 / M4 ARM_CM3_MPU / ARM_CM4_MPU 使用 MPU 保护时选择

  Keil MDK 用户可以重点看 portable/RVDS/(ARM Compiler 5)和 portable/ARMClang/(ARM Compiler 6,复用 GCC 端口)。具体选哪个目录,最终要以芯片内核、编译器和官方 port 目录的说明为准。

  portable/ 的作用可以概括为以下几点:

  • 硬件抽象:将 FreeRTOS 内核与具体硬件隔离。
  • 编译器适配:处理不同编译器的汇编语法和调用约定。
  • 架构优化:针对特定处理器优化任务切换和中断处理。
  • 内存管理:提供可配置的内存分配方案。

2.5 MemMang 内存管理

  portable/MemMang/ 提供了 5 种动态内存管理方案,通过选择其中一个 heap_x.c 参与编译来使用:

1
2
3
4
5
6
MemMang/
├── heap_1.c # 只分配,不释放
├── heap_2.c # 最佳匹配,不合并空闲块
├── heap_3.c # 封装标准 malloc/free
├── heap_4.c # 相邻空闲块合并(最常用)
└── heap_5.c # 多内存区域管理
方案 支持释放 碎片处理 特点与适用场景
heap_1 否 无 只分配不释放,实现最简单、确定性最好;适合任务和队列在启动时全部创建、运行中不删除的系统
heap_2 是 不合并相邻空闲块 使用最佳匹配算法,容易产生碎片;新项目一般直接用 heap_4
heap_3 是 由 C 库决定 封装标准 malloc/free,需要提供线程安全的 C 库;不使用 configTOTAL_HEAP_SIZE
heap_4 是 合并相邻空闲块 最常用,碎片较少;适合一般动态创建、删除任务的场景
heap_5 是 合并相邻空闲块 在 heap_4 基础上支持多个不连续内存区域,适合内部 RAM + 外部 RAM 的组合

  这 5 种方案都提供统一的 pvPortMalloc() / vPortFree() 接口,应用代码不需要关心底层实现。选择哪种方案、如何配置 configTOTAL_HEAP_SIZE,在后续的内存管理文章中会详细展开;这里只需要记住:内存管理也属于 portable 层的一部分,并不是内核强制的固定实现。

3 FreeRTOSConfig.h:应用侧配置

3.1 配置文件的位置与作用

  FreeRTOS 内核本身不包含 FreeRTOSConfig.h,它由应用工程提供。内核的 include/FreeRTOS.h 中会直接 #include "FreeRTOSConfig.h",因此这个文件必须位于编译器的头文件搜索路径中,文件名也不能修改。官方模板位于 FreeRTOS-Kernel/examples/template_configuration/FreeRTOSConfig.h,复制到自己的工程中修改即可。

  FreeRTOSConfig.h 是应用和内核之间的“合同”:应用通过它选择调度策略、时钟频率、内存方案、可选组件和中断优先级等;内核通过 configASSERT 和 #error 检查配置是否完整、是否冲突。把配置放在应用侧,不修改内核源码,升级内核时只需要对比配置变化,维护成本更低。

3.2 常用配置分类

  FreeRTOSConfig.h 里的宏很多,可以按用途分类理解:

配置类别 常用宏 说明
硬件/时钟 configCPU_CLOCK_HZ、configTICK_RATE_HZ、configTICK_TYPE_WIDTH_IN_BITS CPU 频率、系统节拍频率、TickType_t 的位宽
调度策略 configUSE_PREEMPTION、configUSE_TIME_SLICING、configUSE_TICKLESS_IDLE 抢占/协作调度、同优先级时间片、低功耗 Tickless 模式
任务 configMAX_PRIORITIES、configMINIMAL_STACK_SIZE、configMAX_TASK_NAME_LEN 优先级数量、空闲任务栈大小(单位是字)、任务名长度
内存 configSUPPORT_STATIC_ALLOCATION、configSUPPORT_DYNAMIC_ALLOCATION、configTOTAL_HEAP_SIZE、configAPPLICATION_ALLOCATED_HEAP 静态/动态分配、堆大小、堆是否由应用提供
通信与同步 configUSE_MUTEXES、configUSE_RECURSIVE_MUTEXES、configUSE_COUNTING_SEMAPHORES、configUSE_QUEUE_SETS、configUSE_TASK_NOTIFICATIONS 按需开启互斥量、递归互斥量、计数信号量、队列集、任务通知
可选组件 configUSE_TIMERS、configUSE_EVENT_GROUPS、configUSE_STREAM_BUFFERS 软件定时器、事件组、流缓冲区
软件定时器 configTIMER_TASK_PRIORITY、configTIMER_QUEUE_LENGTH、configTIMER_TASK_STACK_DEPTH 定时器服务任务参数,仅在 configUSE_TIMERS=1 时有效
中断/临界区 configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY Cortex-M 端口的节拍/上下文切换中断优先级,以及允许调用 FreeRTOS API 的中断优先级上限(数值不小于该值的中断才能调用 FromISR 接口)
钩子与调试 configUSE_IDLE_HOOK、configUSE_TICK_HOOK、configUSE_MALLOC_FAILED_HOOK、configCHECK_FOR_STACK_OVERFLOW、configASSERT 空闲/节拍/内存分配失败钩子、栈溢出检查、断言
统计与跟踪 configUSE_TRACE_FACILITY、configGENERATE_RUN_TIME_STATS、configUSE_STATS_FORMATTING_FUNCTIONS 任务状态、运行时间统计和调试功能
可选 API INCLUDE_vTaskDelay、INCLUDE_vTaskDelete、INCLUDE_vTaskSuspend、INCLUDE_xTaskGetSchedulerState 等 控制部分 API 是否参与编译,未使用时关闭可以减小代码体积

  这些宏的具体取值与芯片、编译器和工程需求相关,尤其涉及中断优先级的配置不能照搬。本文只建立整体认识,移植和配置细节放在进阶篇。

3.3 头文件的包含顺序

  使用内核 API 时,FreeRTOS.h 必须放在最前面,然后才是 task.h、queue.h 等。因为 FreeRTOS.h 会引入 FreeRTOSConfig.h 并定义 TickType_t、BaseType_t 等基础类型,顺序颠倒可能导致编译失败。

1
2
3
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

  portmacro.h 不在 include/ 目录中,而是位于具体 port 目录(如 portable/GCC/ARM_CM3/portmacro.h),由 portable.h 间接引入,定义端口相关的类型、临界区宏和栈类型。

4 工程组织与使用建议

4.1 最小内核组成

  一个能跑起来的最小 FreeRTOS 工程通常需要以下内容:

类别 必需文件 说明
调度核心 tasks.c、list.c 任务和链表
通信/同步 queue.c 队列、信号量、互斥量的基础;即使只使用任务,实际工程也常保留
移植层 portable/<编译器>/<架构>/port.c、portmacro.h 任务切换、中断入口、临界区
内存管理 portable/MemMang/heap_x.c 使用动态分配时必需;只用静态分配时可以不选
头文件 include/ 内核 API 头文件
配置 FreeRTOSConfig.h 由应用工程提供
按需 timers.c、event_groups.c、stream_buffer.c、croutine.c 分别对应软件定时器、事件组、流/消息缓冲区、协程

  是否编译某个 .c 文件,主要由 FreeRTOSConfig.h 中的功能开关决定。例如 configUSE_TIMERS=0 时不需要软件定时器,configUSE_EVENT_GROUPS=0 时不需要事件组。裁剪前要先确认没有其他模块依赖。

  裁剪能减小代码体积,但会破坏与官方目录结构的一致性,增加升级成本。一般建议保留官方目录结构,只在编译时选择需要的源文件;只有对代码体积非常敏感的项目才做物理裁剪。

4.2 典型工程组织

  在实际项目中,通常把 FreeRTOS 当作中间件,放在单独的目录中管理:

1
2
3
4
5
6
7
8
9
project/
├── app/ # 应用代码:任务、业务逻辑
├── bsp/ # 芯片和外设驱动
├── mw/
│ └── FreeRTOS-Kernel/ # 内核,保持官方目录结构
├── cfg/
│ └── FreeRTOSConfig.h # 应用侧配置
├── CMakeLists.txt
└── ...

  应用层只通过 FreeRTOS.h、task.h 等头文件使用内核;FreeRTOSConfig.h 放在 cfg 等独立目录,和内核源码分开管理。这样升级内核时,只需替换 FreeRTOS-Kernel,配置文件仍可对比、合并。

  FreeRTOS-Kernel 自带 CMakeLists.txt,也可以直接加入 Keil、IAR 或 Makefile 工程。具体如何拷贝文件、添加头文件路径、选择 port 和 heap、处理 SVC_Handler、PendSV_Handler、SysTick_Handler 的冲突,将在进阶篇的移植文章中详细介绍。

4.3 源码阅读入口

  如果准备阅读内核源码,可以从调度器启动流程入手,沿着下面这条线看:

  1. main() 调用 xTaskCreate() 创建任务(tasks.c)。
  2. vTaskStartScheduler() 创建空闲任务、初始化就绪列表,然后调用 xPortStartScheduler()(tasks.c -> port.c)。
  3. xPortStartScheduler() 配置 SysTick 和 PendSV/SVC 优先级,调用 prvPortStartFirstTask(),再通过 SVC 异常进入 vPortSVCHandler(),恢复第一个任务的上下文并开始运行(Cortex-M 端口)。
  4. 每个节拍中断进入 xPortSysTickHandler() -> xTaskIncrementTick();需要切换任务时置位 PendSV,在 xPortPendSVHandler() 中保存和恢复上下文。

  这条线把 tasks.c 和 port.c 串了起来,是理解 FreeRTOS 调度器最直接的入口。任务状态、优先级和时间片的具体规则,在任务管理文章中展开。

5 小结

  本文从工程视角梳理了 FreeRTOS:GitHub 上不同仓库的定位、LTS 集成仓库的子模块结构、内核源码文件的分工、portable 移植层和 MemMang 内存方案,以及应用侧 FreeRTOSConfig.h 的作用。可以把一个 FreeRTOS 工程概括为:内核源码 + 移植层 + 内存管理 + 应用配置。

  具体的移植步骤(拷贝哪些文件、如何配置 CMake、如何处理中断向量冲突)将在进阶篇中介绍。接下来先进入基础篇的下一篇文章——任务管理。