第四章 FreeRTOS源码结构解析

好,咱们开始聊FreeRTOS的源码结构。说实话,我第一次打开FreeRTOS源码包的时候,也有点懵——文件夹不少,文件也挺多。但别怕,这东西其实很有条理。我习惯先看目录结构,再抓核心文件,最后看配置和内存管理。按这个顺序走,你很快就能摸清门道。

4.1 FreeRTOS目录结构

你下载FreeRTOS源码后,解压出来大概长这样:

FreeRTOS/
├── Demo/         # 各种开发板的示例工程
├── Source/       # 核心源码(重点!)
│   ├── include/  # 头文件
│   ├── portable/ # 移植层代码
│   └── *.c       # 核心C文件
├── License/      # 许可证文件
└── ...

我个人觉得,Source目录是重中之重。你想想看,整个RTOS的核心逻辑都在这里。而portable目录,说白了就是让FreeRTOS能在不同芯片上跑起来的“适配层”。我刚开始移植时,就是盯着portable目录里的东西改。

核心目录速览:

  • Source/ — RTOS核心源码,你主要看这里
  • Source/include/ — 所有头文件,API声明都在里面
  • Source/portable/ — 移植层,按编译器/芯片分文件夹
  • Demo/ — 官方示例,可以当参考,但别直接抄

4.2 核心文件说明

核心文件就三个:tasks.cqueue.ctimers.c。嗯,这里要注意,它们仨是FreeRTOS的“三驾马车”。

4.2.1 tasks.c — 任务管理

这是FreeRTOS的心脏。任务创建、删除、调度、挂起、恢复……全在这一个文件里。我遇到过不少新手,一上来就改tasks.c里的调度算法,结果系统直接崩了。我的建议是:别动tasks.c,除非你真的知道自己在干什么。

tasks.c里最重要的几个函数:

  • xTaskCreate() — 创建任务
  • vTaskDelete() — 删除任务
  • vTaskDelay() — 任务延时
  • vTaskStartScheduler() — 启动调度器

我的经验: 调试任务问题时,多看看uxTaskGetStackHighWaterMark()这个函数。它能告诉你任务栈还剩多少空间。我曾经有个项目,任务栈设小了,系统跑几天就死机,全靠这个函数找到问题。

4.2.2 queue.c — 队列与通信

任务之间怎么传数据?靠队列。queue.c实现了消息队列、二值信号量、计数信号量、互斥量……说白了,任务间通信的“管道”都在这里。

核心函数:

  • xQueueCreate() — 创建队列
  • xQueueSend() — 发送数据
  • xQueueReceive() — 接收数据

你想想看,如果没有队列,任务之间就只能靠全局变量传数据,那多危险。我早期一个项目就吃过这个亏——两个任务同时写一个全局变量,数据乱得一塌糊涂。后来改用队列,问题立刻解决。

4.2.3 timers.c — 软件定时器

硬件定时器资源有限,软件定时器就派上用场了。timers.c实现了软件定时器的创建、启动、停止、重置等功能。注意,软件定时器的回调函数是在定时器服务任务中执行的,所以不要在回调里做耗时操作。

警告: 软件定时器的回调函数里,千万别调用vTaskDelay()或阻塞型API。我曾经犯过这个错,结果定时器服务任务被卡住,整个系统的定时器都停了。嗯,血的教训。

4.3 配置文件 FreeRTOSConfig.h 详解

这个文件是FreeRTOS的“开关面板”。你通过它来裁剪系统功能、配置参数。每个项目都要有一个自己的FreeRTOSConfig.h。

我习惯把配置项分成三类:

类别 典型宏定义 说明
基础配置 configUSE_PREEMPTION 是否启用抢占式调度(1=启用)
内存配置 configTOTAL_HEAP_SIZE 堆大小(单位字节)
功能裁剪 configUSE_TIMERS 是否启用软件定时器

举个例子,一个典型的配置片段:

#define configUSE_PREEMPTION        1
#define configCPU_CLOCK_HZ          ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ          ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES        ( 5 )
#define configMINIMAL_STACK_SIZE    ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE       ( ( size_t ) ( 17 * 1024 ) )
#define configUSE_TIMERS            1
#define configTIMER_TASK_PRIORITY   ( 2 )

关键点:

  • configCPU_CLOCK_HZ 必须和你的芯片主频一致,否则时间全乱套
  • configTICK_RATE_HZ 一般设1000,即1ms一个tick。设太高会增加系统开销
  • configTOTAL_HEAP_SIZE 要根据你的任务数量和栈大小来算,别设太小

4.4 内存管理方案(heap_1 ~ heap_5)

FreeRTOS提供了5种内存管理方案,分别放在portable/MemMang/目录下。你只需要选一个合适的,把对应的.c文件加到工程里就行。

我简单说说每个方案的特点:

方案 特点 适用场景
heap_1 最简单,只分配不释放 任务和队列创建后永不删除
heap_2 支持释放,但可能产生碎片 偶尔动态创建/删除任务
heap_3 直接调用标准库malloc/free 需要线程安全,且编译器支持
heap_4 合并相邻空闲块,减少碎片 大多数项目推荐使用
heap_5 支持多个不连续的内存区域 芯片有多个RAM区域

我个人最常用的是heap_4。为什么呢?因为它能合并相邻的空闲内存块,碎片问题比heap_2好很多。我曾经在一个项目里用heap_2,跑了一个月后内存碎片严重,系统再也创建不了新任务。换成heap_4后,问题再没出现过。

选型建议:

  • 如果你的任务和队列都是静态创建的(不动态删除),用heap_1最省心
  • 如果需要动态创建和删除,优先选heap_4
  • 如果芯片有多个RAM区域(比如内部SRAM和外部SDRAM),用heap_5

嗯,关于内存管理,我再多说一句。你可以在FreeRTOSConfig.h里定义configUSE_MALLOC_FAILED_HOOK,然后实现一个钩子函数。当内存分配失败时,系统会自动调用这个函数。我一般会在钩子里点亮一个LED或者打印错误信息,这样调试时能第一时间发现问题。

好了,这一章的内容就到这儿。下一章咱们开始动手移植,我会带着你一步步把FreeRTOS跑起来。