p16(11.6 分钟)、p17(11.5 分钟)
源码目录
FreeRTOS 源码不用一次读完,先建立一张地图:
Source/├── tasks.c 任务、调度、延时、状态├── list.c 内核链表,任务队列的基础├── queue.c 队列、队列集、信号量、互斥量├── timers.c 软件定时器├── event_groups.c 事件组├── include/ API 与内核结构声明└── portable/ 芯片和编译器相关实现portable/ 是最容易出问题的地方,里面同时有编译器相关(RVDS、GCC、IAR)和芯片相关(Cortex-M3、M4)的代码,选错目录编译或链接阶段就会直接报错。这类问题不该靠改内核源码绕过去。
p16 这一集没有抓到字幕,上面这部分是按 FreeRTOS 官方源码结构整理的,不是视频里的原话。
堆就是一个数组
CubeMX 的中间件配置里,内存分配有「动态分配」和「静态分配」两个选项,堆大小是 3072 字节,管理方案选 heap_4。把动态分配禁掉之后,配置文件里就不再去编译 heap_4 那个文件了。
堆在代码里没什么神秘的东西,就是一个数组:
uint8_t ucHeap[configTOTAL_HEAP_SIZE];一个全局静态数组,大小由配置宏决定(这里是 3072),用数组巧妙地表示一整块空闲内存。宏开得非常大时编译会报错。
heap_1 到 heap_5
五个文件都在 Third-party/FreeRTOS/Source/portable/MemMang 下面,每个都实现了同名函数(pvPortMalloc 之类),所以同一时间只能用其中一个。
heap_1 只分配、不回收,跟前面手写的最简 my_malloc 是一个路子。只分配不回收的应用用它是一点空间都不浪费——要 100 字节就给 100 字节,没有头部开销。
heap_2 能分配也能释放,但不合并相邻的空闲块,碎片问题很严重:
分配 100(头部 + 100)、分配 50(头部 + 50)释放 buffer1、释放 buffer2两块明明相邻,却不合并此时想分配 120 → 失败heap_3 只是去调用标准库函数(malloc/free)。嵌入式里一般不用标准库,所以这个方案也很少用。
heap_4 在 heap_2 基础上加了合并:释放 100 和 50 之后发现相邻就合并,再分配 120 就没问题了。
heap_5 支持分隔的内存。硬件上有 CPU、片内内存、Flash,可能还有片外 RAM,程序只用了其中一小块,剩下的都能当堆。heap_5 用空闲链表管理这些离散的堆,链表头指向第一个堆、再指向第二个,所以要先告诉它一共有几个离散堆,并把每块内存的起止地址和大小初始化进链表。
选哪个:一般用 heap_4,有多块内存用 heap_5,heap_2 很少用,heap_1 更少用。
heap_4 用起来要注意什么
初始化堆的私有函数是 prvHeapInit,但不需要手工调用——第一次调 pvPortMalloc 的时候会自动调。
分配和释放就是 pvPortMalloc / vPortFree。
想知道 3072 够不够,跑一段时间后调这两个函数看:
xPortGetFreeHeapSize() 当前还剩多少空闲内存xPortGetMinimumEverFreeHeapSize() 整个运行过程中空闲容量的最小值第二个值更有用。如果它已经接近个位数或十位数,说明系统曾经紧张到这个程度,该把堆调大了。
pvPortMalloc 还留了个口子:用户可以自定义一个钩子函数,分配失败的时候被调用,用来打印调试信息。
坑
configTOTAL_HEAP_SIZE是 FreeRTOS 堆的大小,不是芯片全部 RAM。- 动态创建的任务、队列、信号量都从这个堆里拿内存。堆不够时表现为创建失败,不是运行出错。
- 只看
xPortGetFreeHeapSize()会误判。刚启动时剩得多不代表跑起来够用,要看历史最小值。 - 五份 heap_x 文件只能挑一个编进工程,同时编进去会撞同名函数。
请输入编辑凭据,只有站点所有者可以修改文章。