FreeRTOS是作为一组C源文件提供的,因此成为一名合格的C程序员是使用FreeRTOS的先决条件,因此本章假设读者熟悉以下概念:
如何构建C项目,包括不同的编译和链接阶段。
堆栈和堆是什么。
标准的C库malloc()和free()函数。
动态内存分配及其与FreeRTOS的相关性
后续章节将介绍内核对象,如任务、队列、信号量和事件组。为了使FreeRTOS尽可能易于使用,这些内核对象不是在编译时静态分配的,而是在运行时动态分配的;
FreeRTOS在每次创建内核对象时分配RAM,并在每次删除内核对象时释放RAM。此策略减少了设计和规划工作量,简化了API,并最大限度地减少了RAM占用。
本章讨论动态内存分配。动态内存分配是一个C编程概念,而不是FreeRTOS或多任务处理特有的概念。它与FreeRTOS有关,因为内核对象是动态分配的,通用编译器提供的动态内存分配方案并不总是适用于实时应用程序。
可以使用标准的C库malloc()和free()函数分配内存,但由于以下一个或多个原因,它们可能不合适或不合适:
它们并不总是适用于小型嵌入式系统。
它们的实现可能相对较大,占用宝贵的代码空间。
它们很少是线程安全的。
它们不是决定性的;执行函数所需的时间将因调用而异。
它们可能会出现碎片化
(如果堆内的空闲RAM被分解为彼此分离的小块,则认为堆是碎片化的。如果堆是碎片化的,那么如果堆中没有一个空闲块足够大来包含该块,即使堆中所有单独的空闲块的总大小比无法分配的块的大小大很多倍,分配块的尝试也会失败。)。
它们会使链接器配置复杂化。
如果允许堆空间增长到其他变量使用的内存中,它们可能会成为难以调试的错误的来源。
FreeRTOS现在将内存分配视为可移植层的一部分(而不是核心代码库的一部分)。这是因为认识到不同的嵌入式系统具有不同的动态内存分配和时序要求,因此单个动态内存分配算法只适用于应用程序的一个子集。此外,从核心代码库中删除动态内存分配使应用程序编写者能够在适当的时候提供自己的特定实现。
当FreeRTOS需要RAM时,它不会调用malloc(),而是调用pvPortMalloc()。当RAM被释放时,内核调用vPortFree()而不是调用free()。pvPortMalloc()具有与标准C库malloc()函数相同的原型,vPortFree()与标准C库free()函数具有相同的原型。
pvPortMalloc()和vPortFree()是公共函数,因此也可以从应用程序代码中调用.
FreeRTOS附带了pvPortMalloc()和vPortFree()的五个示例实现,所有这些都在本章中进行了记录。FreeRTOS应用程序可以使用其中一个示例实现,也可以提供自己的实现。
这五个示例分别在heap_1.c、heap_2.c、heap\o3.c、heap_4.c和heap_5.c源文件中定义,所有这些源文件都位于FreeRTOS/source/portable/MemMang目录中。
内存分配方案示例
Heap_1
小型专用嵌入式系统通常在调度程序启动之前只创建任务和其他内核对象。在这种情况下,内存只在应用程序开始执行任何实时功能之前由内核动态分配,内存在应用程序的生命周期内保持分配状态。这意味着所选的分配方案不必考虑任何更复杂的内存分配问题,如确定性和碎片化,而可以只考虑代码大小和简单性等属性。
Heap_1.c实现了pvPortMalloc()的一个非常基本的版本,但没有实现vPortFree()。从不删除任务或其他内核对象的应用程序有可能使用heap_1。
一些商业关键和安全关键系统也有可能使用heap_1,否则这些系统将禁止使用动态内存分配。关键系统通常禁止动态内存分配,因为存在与非确定性、内存碎片和失败分配相关的不确定性,但Heap_1始终是确定性的,不能对内存进行碎片化。
heap_1分配方案在调用pvPortMalloc()时将简单数组细分为更小的块。该数组称为FreeRTOS堆。
数组的总大小(以字节为单位)由FreeRTOSConfig.h中的configTOTAL_HEAP_size定义设置。以这种方式定义大型数组会使应用程序看起来消耗大量RAM,甚至在从数组分配任何内存之前。
每个创建的任务都需要从堆中分配一个任务控制块(TCB)和一个堆栈。图5演示了heap_1如何在创建任务时细分简单数组。
A显示了在创建任何任务之前的数组——整个数组都是空闲的。
B显示了创建一个任务后的数组。
C显示了创建三个任务后的数组。