FreeRTOS源码分析
FreeRTOS源码分析
一、FreeRTOS源码文件目录结构

Middlewares\Third_Party\FreeRTOS\Source
Source 目录是 FreeRTOS 的核心部分,包含内核源码和头文件。
-
include 子目录包含 FreeRTOS 的核心头文件,提供 API 和数据类型的定义。
-
portable 子目录包含针对特定处理器架构的移植代码,例如 ARM、MIPS 等。开发者需要根据目标处理器选择对应的子目录。
-
内存管理文件位于 portable/MemMang,提供多种堆实现方式,如 heap_1 到 heap_5,分别支持不同的内存管理需求。
-
核心文件包括 tasks.c(任务操作)、list.c(列表操作)和 queue.c(队列操作)。其他可选功能文件如 timers.c(软件定时器)和 event_groups.c(事件组)。
Source\portable\RVDS\ARM_CM3\移植层核心文件:
port.c:架构相关实现(任务切换、中断管理、SysTick 配置等核心逻辑);portmacro.h:架构相关宏定义(如任务栈类型、中断开关函数);这两个文件是移植的关键,必须根据项目的编译器和 MCU 架构选择对应目录下的文件。
二、FreeRTOS源码文件核心文件
存放内核实现代码,编译时需将这些文件加入项目(无需全部添加,按需裁剪)
核心文件对应功能如下:
| 文件名 | 对应功能模块 | 是否必选 |
|---|---|---|
| task.c | 任务管理(创建、调度、挂起 / 恢复等核心逻辑) | 是 |
| queue.c | 消息队列(数据传递、同步)+ 信号量 / 互斥锁(基于队列实现) | 是(核心) |
| timer.c | 软件定时器(基于系统节拍的虚拟定时器) | 否(按需) |
| event_groups.c | 事件组(多事件同步) | 否(按需) |
| croutine.c | 协程(轻量级任务,适用于资源极受限场景,极少用) | 否(按需) |
| stream_buffer.c | 流缓冲区(用于大量数据传输,如串口数据接收) | 否(按需) |
| heap_1.c ~ heap_5.c | 5 种内存管理实现(选 1 种即可,如 heap_4.c 支持内存碎片合并) |
是 |
三、移植时涉及的文件
FreeRTOS 移植文件的核心存放逻辑就是 「按编译器 + MCU 架构分类」,portable/[compiler]/[architecture] 目录是连接内核与硬件的关键,里面的文件直接决定 FreeRTOS 能否在目标 MCU 上运行。
| 层级 | 含义(作用) | 常见取值 / 示例 |
|---|---|---|
[compiler] |
编译器 / 开发工具链,即你使用的编译环境 | RVDS(Keil MDK 早期名称,兼容 Keil5)、GCC(ARM-GCC)、IAR(IAR Embedded Workbench) |
[architecture] |
MCU 的内核架构(而非具体型号),聚焦 CPU 核心指令集、寄存器、中断机制 | ARM_CM3(Cortex-M3)、ARM_CM4F(Cortex-M4F,带 FPU)、ARM_CM7(Cortex-M7)、ARM_CM0(Cortex-M0) |
实例:
portable/RVDS/ARM_CM3:适用于 Keil5 编译器 + Cortex-M3 架构 MCU(如 STM32F103)portable/GCC/ARM_CM4F:适用于 ARM-GCC 编译器 + Cortex-M4F 架构 MCU(如 STM32F407)portable/IAR/ARM_CM7:适用于 IAR 编译器 + Cortex-M7 架构 MCU(如 STM32H743)
关键提醒:
- Keil5 虽然现在叫 MDK-ARM,但移植目录仍沿用
RVDS(历史原因,RVDS 是 Keil 的前身工具链),选择时直接选RVDS/[对应架构]即可; - 带
F的架构目录(如 ARM_CM4F)是专门适配带浮点单元(FPU)的 MCU,若 MCU 支持硬件浮点,必须选带F的目录,否则浮点运算会异常。
每个 [compiler]/[architecture] 目录下,核心文件只有 2 个,这是移植的核心文件:
| 文件名 | 作用说明(FreeRTOS 与硬件交互的核心) |
|---|---|
port.c |
1. 任务切换实现:负责保存 / 恢复任务上下文(寄存器值、栈指针),是 FreeRTOS 多任务调度的硬件基础;2. 系统节拍(SysTick)配置:初始化 SysTick 定时器,生成系统节拍中断(任务调度的触发源);3. 中断管理:实现 FreeRTOS 中断开关函数(如 portDISABLE_INTERRUPTS()),确保调度器安全运行;4. 栈初始化:定义任务栈的初始化方式(如动态创建任务时的栈空间分配)。 |
portmacro.h |
1. 架构相关宏定义:任务优先级类型(UBaseType_t)、栈大小单位(portSTACK_TYPE)、中断优先级分组宏等;2. 汇编指令封装:将关键硬件操作(如中断开关、任务切换触发)封装为宏,供内核调用;3. 编译器适配:定义编译器特定的关键字(如 __irq 中断函数修饰符)、内存对齐方式。 |
补充文件(部分目录有,辅助功能):
portasm.s(或.asm):部分架构目录会将任务切换的汇编代码单独抽出(如 ARM_CM3 架构),本质是port.c的汇编实现部分,必须与port.c配套使用;heap_x.c:内存管理实现(如 heap_4.c),不放在这个目录下,而是在portable/MemMang/目录,属于通用移植代码,所有架构共用,选 1 个添加到项目即可。
四、头文件目录
| 目录路径 | 核心作用 | 关键文件 / 意义 |
|---|---|---|
Middlewares\Third_Party\FreeRTOS\Source\include |
内核通用头文件目录(所有平台共用) | 包含 FreeRTOS.h(内核主头文件)、task.h(任务 API)、queue.h(队列 API)等,是调用 FreeRTOS 功能的基础。 |
Middlewares\Third_Party\FreeRTOS\Source\portable\[compiler]\[architecture] |
硬件移植头文件目录(编译器 + 架构专属) | 仅包含 portmacro.h(移植层核心头文件),定义架构相关宏(如中断开关、栈类型),内核通过它适配具体硬件。 |
Core\Inc(或自定义目录) |
项目专属配置文件目录 | 仅包含 FreeRTOSConfig.h(唯一项目专属文件),用于裁剪内核功能、配置时钟 / 优先级 / 堆大小等,是 FreeRTOS 适配项目的关键。 |
五、入口函数
在Core\Src\main.c的main函数里,初始化了FreeRTOS环境、创建了默认任务,然后启动调度器
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize(); // 初始化FreeRTOS的运行环境
/* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init(); // 负责完成FreeRTOS的各项初始化工作,包括默认任务创建、队列初始化、信号量
设置等,为FreeRTOS调度器的启动做好准备
/* Start scheduler */
osKernelStart(); // 启动调度器
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
六、数据类型与编码规范
FreeRTOS 为了保证跨平台兼容性和代码可读性,定义了一套统一的数据类型和编码规范,核心原则是 “屏蔽硬件差异、规范命名风格、降低维护成本”。
数据类型
FreeRTOS 不直接使用 int、long 等原生 C 数据类型(不同架构下长度可能不同,如 16 位 MCU 的 int 是 16 位,32 位 MCU 是 32 位),而是通过宏定义封装了一套平台无关的数据类型,所有类型定义在 FreeRTOS.h 中。
#ifndef INC_FREERTOS_H
#define INC_FREERTOS_H
/*
* Include the generic headers required for the FreeRTOS port being used.
*/
#include <stddef.h>
/*
* If stdint.h cannot be located then:
* + If using GCC ensure the -nostdint options is *not* being used.
* + Ensure the project's include path includes the directory in which your
* compiler stores stdint.h.
* + Set any compiler options necessary for it to support C99, as technically
* stdint.h is only mandatory with C99 (FreeRTOS does not require C99 in any
* other way).
* + The FreeRTOS download includes a simple stdint.h definition that can be
* used in cases where none is provided by the compiler. The files only
* contains the typedefs required to build FreeRTOS. Read the instructions
* in FreeRTOS/source/stdint.readme for more information.
*/
#include <stdint.h> /* READ COMMENT ABOVE. */
#ifdef __cplusplus
extern "C" {
#endif
/* Application specific configuration options. */
#include "FreeRTOSConfig.h"
/* Basic FreeRTOS definitions. */
#include "projdefs.h"
/* Definitions specific to the port being used. */
#include "portable.h"
/* Must be defaulted before configUSE_NEWLIB_REENTRANT is used below. */
#ifndef configUSE_NEWLIB_REENTRANT
#define configUSE_NEWLIB_REENTRANT 0
#endif
/* Required if struct _reent is used. */
#if ( configUSE_NEWLIB_REENTRANT == 1 )
#include <reent.h>
#endif
基础数据类型
| FreeRTOS 数据类型 | 本质(32 位架构) | 核心用途 |
|---|---|---|
TickType_t |
uint32_t |
系统节拍计数类型,用于 vTaskDelay()、xQueueSend() 等函数的超时参数 |
BaseType_t |
int32_t |
函数返回值类型(如 pdPASS/pdFALSE)、布尔型判断(替代 bool) |
UBaseType_t |
uint32_t |
无符号基础类型,用于任务优先级、队列长度等无符号数值 |
StackType_t |
uint32_t |
任务栈元素类型,由移植层 portmacro.h 定义(与 MCU 寄存器宽度一致) |
TaskHandle_t |
void* 或结构体指针 |
任务句柄(标识任务的唯一指针),用于 vTaskSuspend()、vTaskDelete() 等 |
QueueHandle_t |
void* 或结构体指针 |
队列句柄,用于队列相关 API 调用 |
SemaphoreHandle_t |
QueueHandle_t |
信号量 / 互斥锁句柄(FreeRTOS 信号量基于队列实现,复用队列句柄类型) |
TimerHandle_t |
void* 或结构体指针 |
软件定时器句柄 |
常用宏定义
| 宏定义 | 含义 | 用途示例 |
|---|---|---|
pdPASS |
函数执行成功(值为 1) | if (xQueueSend() == pdPASS) |
pdFALSE |
逻辑假(值为 0) | BaseType_t xFlag = pdFALSE; |
pdTRUE |
逻辑真(值为 1) | if (xFlag == pdTRUE) |
portMAX_DELAY |
无限等待(值为 0xFFFFFFFF) | xQueueSend(queue, &item, portMAX_DELAY) |
NULL |
空指针 | TaskHandle_t xTask = NULL; |
编码规范
函数名前缀含义
FreeRTOS 函数名前缀有明确规则,通过前缀可快速判断函数的返回值类型和模块:
| 前缀 | 含义(返回值 / 功能) | 模块示例 |
|---|---|---|
v |
返回值为 void |
vTaskDelay()、vQueueDelete() |
x |
返回值为 BaseType_t 或句柄 |
xTaskCreate()(返回 pdPASS/pdFALSE)、xQueueCreate()(返回队列句柄) |
ul |
返回值为 unsigned long |
ulTaskGetTickCount()(返回当前节拍数) |
c |
返回值为 char |
cQueueReceiveFromISR()(较少用) |
pv |
返回值为 void* |
pvPortMalloc()(内存分配函数) |
prv |
私有函数(仅内核内部调用) | prvTaskSwitchContext()(任务切换函数,用户不可调用) |
命名规范
| 元素类型 | 命名规则 | 示例 |
|---|---|---|
| 函数名 | 小写前缀 + 大写单词拼接(前缀标识模块) | vTaskDelay()(v=void 返回值,Task= 模块)、xQueueSend()(x= 返回 BaseType_t / 句柄) |
| 数据类型名 | 大写前缀 + 大写单词拼接,末尾加 _t |
TickType_t、BaseType_t、TaskHandle_t |
| 宏定义 | 全大写,单词间用下划线分隔 | pdPASS、configTICK_RATE_HZ、portMAX_DELAY |
| 全局变量 | 小写 g_ 前缀 + 大写单词拼接 |
g_xTaskList(全局任务列表) |
| 局部变量 | 小写字母,单词间用下划线分隔(或驼峰) | xResult、pxMessage(p= 指针,x= 类型标识) |
| 指针变量 | 前缀加 p(或 px,x 标识类型) |
pxTaskHandle(指向任务句柄的指针)、pcBuffer(c=char 类型) |
结语:
无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)