概述
本文对FreeRTOS源码进行概述,包括其核心文件作用,及其编程规范,有助于阅读rtos的内核源码,更好的帮助理解。
一、FreeRTOS 源码核心结构概述
FreeRTOS 是轻量级实时操作系统,核心功能围绕 “任务调度” 和 “任务间通信” 展开,源码结构清晰,可分为内核核心文件和可选组件:
类别 | 核心文件 | 主要功能 |
---|---|---|
任务管理 | task.c |
任务创建(xTaskCreate() )、删除、挂起 / 恢复,以及核心的调度器实现(vTaskStartScheduler() )。 |
调度器核心 | 同上(调度逻辑嵌入task.c ) |
基于优先级的抢占式调度(可配置为时间片轮转),上下文切换(portYIELD() )。 |
队列 / 通信 | queue.c /list.c |
实现队列(任务间数据传递)、信号量(SemaphoreHandle_t )、互斥锁(MutexHandle_t )等。 |
时间管理 | timers.c |
软件定时器(xTimerCreate() ),基于系统时基触发回调函数。 |
配置文件 | FreeRTOSConfig.h |
内核裁剪配置(如任务最大优先级、栈大小、钩子函数使能等),是适配硬件的关键。 |
硬件接口 | port.c /portmacro.h |
与 CPU 架构相关的底层实现(如 ARM Cortex-M 的上下文切换、中断处理),由芯片厂商适配。 |
二、STM32CubeMX 生成的 RTOS 中间件文件及作用
CubeMX 会自动集成 FreeRTOS 源码,并生成适配 STM32 的配置文件和初始化代码,核心文件如下(以 STM32 工程为例):
-
FreeRTOSConfig.h
- 作用:FreeRTOS 的 “开关面板”,由 CubeMX 根据用户配置(如任务优先级数量、栈大小、是否启用互斥锁等)自动生成。
- 关键配置项:
configMAX_PRIORITIES
:最大任务优先级(如5
,数值越大优先级越高);configTOTAL_HEAP_SIZE
:内核堆大小(任务、队列等动态内存从这里分配);configUSE_PREEMPTION
:是否启用抢占式调度(1
启用,实时性核心);configUSE_IDLE_HOOK
:是否启用空闲任务钩子函数(自定义空闲任务行为)。
-
MX_FreeRTOS_Init.c
/MX_FreeRTOS_Init.h
-
作用:用户任务的初始化入口,CubeMX 会在此文件中生成任务创建代码(基于用户在 CubeMX 中配置的任务)。
-
典型内容:
void MX_FreeRTOS_Init(void) {// 创建任务(参数:任务函数、名称、栈大小、参数、优先级、任务句柄)xTaskCreate(StartDefaultTask, "DefaultTask", 128, NULL, 1, &DefaultTaskHandle);// 若配置了其他任务(如Task1、Task2),会在此处继续创建 }
-
-
stm32f1xx_it.c
(中断服务函数文件)-
作用:集成 FreeRTOS 的中断适配,主要是系统时基中断(通常用 SysTick 定时器)。
-
关键代码:
void SysTick_Handler(void) {HAL_IncTick(); // HAL库时基if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {xPortSysTickHandler(); // 调用FreeRTOS的SysTick处理函数,用于任务调度计时} }
-
-
FreeRTOS 官方源码文件
CubeMX 会将task.c
、queue.c
、timers.c
等核心文件复制到工程的Middlewares/Third_Party/FreeRTOS/Source
目录下,与硬件无关的逻辑保持不变。
三、FreeRTOS 源码的 “入口函数”
FreeRTOS 的运行始于调度器启动,整体流程如下(结合 CubeMX 生成的代码):
-
用户程序入口:
main()
函数
CubeMX 生成的main.c
中,main()
先初始化硬件(HAL_Init()
、时钟配置等),再调用MX_FreeRTOS_Init()
创建任务,最后启动调度器:/* Init scheduler */ osKernelInitialize(); /* 初始化FreeRTOS运行环境 */ MX_FREERTOS_Init(); /* 创建任务 *//* Start scheduler */ osKernelStart(); /* 启动调度器 里面调用vTaskStartScheduler()函数*/
-
调度器启动:
vTaskStartScheduler()
位于task.c
,是 FreeRTOS 内核真正开始工作的入口,主要做三件事:- 初始化内核数据结构(任务链表、就绪列表、堆内存等);
- 创建空闲任务(
prvIdleTask
,优先级最低,用于释放删除的任务内存); - 触发第一次上下文切换(
portRESTORE_CONTEXT()
),切换到最高优先级的就绪任务执行。
-
第一个执行的任务
调度器启动后,会从 “就绪列表” 中选择优先级最高的任务执行。若用户在MX_FreeRTOS_Init()
中创建了StartDefaultTask
(默认任务),且它是最高优先级,则第一个执行的就是该任务。
四、FreeRTOS 源码的 “编程规则”
1、基础数据类型的前缀规则
FreeRTOS 不直接使用 C 语言原生类型(如int
、long
,因不同平台长度可能不同),而是通过自定义类型实现跨平台,这些类型的前缀有明确含义:
前缀 | 含义 | 对应原生类型(举例) | 用途场景 |
---|---|---|---|
u |
Unsigned(无符号) | unsigned char 、unsigned int |
无符号整数(如计数、长度) |
s |
Signed(有符号) | signed char 、signed int |
有符号整数(如差值、偏移量) |
x |
结构体 / 枚举 / 大型类型(自定义) | 无固定原生类型(如任务控制块、队列) | 复杂结构(任务、队列、事件组) |
p |
Pointer(指针) | 任意类型的指针 | 指向变量、数组、结构体的指针 |
ul |
Unsigned Long(无符号长整数) | unsigned long (通常 32 位) |
较大的无符号数值(如系统时间) |
l |
Long(有符号长整数) | long (通常 32 位) |
较大的有符号数值 |
2、典型组合示例(前缀 + 类型)
这些组合在源码中高频出现,掌握后能快速识别变量用途:
变量 / 类型示例 | 前缀解析 | 含义说明 |
---|---|---|
uint8_t |
u (无符号)+ int8 |
8 位无符号整数(等价于unsigned char ) |
int32_t |
s (隐含)+ int32 |
32 位有符号整数(等价于int32_t ) |
pucBuffer |
p (指针)+ u (无符号)+ c (字符) |
指向无符号字符数组的指针(缓冲区指针) |
pxTaskTCB |
p (指针)+ x (结构体) |
指向任务控制块(TCB,结构体)的指针 |
ulTickCount |
ul (无符号长整数) |
以系统时基为单位的计数器(如uint32_t ) |
xQueueHandle |
x (自定义类型) |
队列句柄(本质是指向队列结构体的指针) |
s16Error |
s (有符号)+ 16 (16 位) |
16 位有符号错误码 |
3、FreeRTOS 核心自定义类型(带固定前缀)
除了基础类型,FreeRTOS 定义了大量用于特定功能的类型,其命名也遵循规则:
自定义类型 | 前缀 / 后缀 | 含义与用途 |
---|---|---|
BaseType_t |
Base (基础) |
平台默认的 “基础整数类型”(通常是 32 位有符号),作为多数 API 的返回值类型(如成功返回pdPASS )。 |
TickType_t |
Tick (时基) |
用于表示系统时基( ticks )的类型(通常是uint32_t ),如vTaskDelay() 的参数类型。 |
TaskHandle_t |
Handle_t (句柄) |
任务句柄(本质是指向TCB_t 结构体的指针),用于操作任务(如vTaskSuspend(TaskHandle_t) )。 |
QueueHandle_t |
Handle_t |
队列句柄(指向队列结构体的指针),用于队列操作(如xQueueSend(QueueHandle_t) )。 |
SemaphoreHandle_t |
Handle_t |
信号量句柄(与队列句柄通用,因信号量基于队列实现)。 |
TCB_t |
TCB (任务控制块) |
任务控制块结构体(x 类型),存储任务的所有信息(栈、优先级、状态等)。 |
4、特殊标识:宏定义与常量的前缀
FreeRTOS 的宏和常量也有前缀规则,用于区分功能:
前缀 | 含义 | 示例 |
---|---|---|
pd |
Portable Define(可移植定义) | pdTRUE (真)、pdFALSE (假)、pdPASS (成功)、pdFAIL (失败)。 |
config |
配置项(来自FreeRTOSConfig.h ) |
configMAX_PRIORITIES (最大优先级)、configTICK_RATE_HZ (时基频率)。 |
err |
错误码 | errQUEUE_FULL (队列满)、errTIMEOUT (超时)。 |
5、通过前缀快速理解源码的技巧
- 看到
p
开头的变量:立即识别为指针(如pxCurrentTCB
是当前任务 TCB 的指针)。 - 看到
x
开头的类型 / 变量:通常是结构体或复杂类型(如xTaskCreate()
返回的是BaseType_t
,但xQueue
是队列结构体实例)。 - 看到
ul
开头的变量:多为 32 位无符号整数(如ulTaskNumber
表示任务编号)。 - 看到
Handle_t
结尾的类型:均为 “句柄”,本质是指针,用于操作内核对象(任务、队列等)。
五、阅读源码的建议路径
- 从
main()
开始,跟踪到freertos.c文件,看到vTaskStartScheduler()
,理解调度器启动流程; - 重点看
task.c
中的xTaskCreate()
(任务创建)和vTaskSwitchContext()
(任务切换); - 结合
FreeRTOSConfig.h
的配置项,理解 “可裁剪” 特性(如关闭某个功能后,对应源码不参与编译); - 先忽略硬件相关的
port.c
,聚焦通用逻辑(任务、队列),再深入架构适配细节。