从FreeRTOS V9.0.0开始内核对象既可以在编译的时候静态分配,也可以在运行时动态分配。为了尽可能让FreeRTOS易于使用,这些内核对象并不是在编译时静态分配的,而是在运行时动态分配的。内核对象创建时FreeRTOS分配RAM而在内核对象删除时释放内存。这样简化了API,并且减少了RAM的占用。
动态内存分配是C语言编程的概念,而不是针对FreeRTOS或者多任务编程的概念。它和FreeRTOS是相关的,因为内核对象是动态分配的,并且通用编译器提供的动态内存分配方案对于实时应用程序并不总是适合的。
内存可以使用标准C库的malloc()和free()函数来分配,但直接调用这两个函数可能不适合,原因有以下几点:
1. malloc()和free()函数在小型嵌入式系统中并不总是可用的
2. 它们的实现可能非常的大,占据了相当大的一块代码空间
3. 他们几乎都不是线程安全的
4. 它们并不是确定的,每次调用这些函数执行的时间可能都不一样
5. 它们有可能产生碎片
6. 它们有可能打乱链接器的配置
7. 如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为debug的灾难
动态内存分配的可选
从FreeRTOS V9.0.0开始内核对象既可以在编译时静态分配也可以在运行时动态分配。如今FreeRTOS把内存分配放在可移植层。这是考虑到不同的嵌入式系统有不同的动态内存管理方法和时间要求,因此单个的动态内存分配算法将只适合于应用程序的一个子集。同样,如果有需要的话,开发人员可以从核心代码库中移除动态内存分配。
当FreeRTOS需要RAM的时候,并不是调用malloc(),而是调用pvPortMalloc()。当需要释放RAM的时候,并不是调用free(),而是调用vPortFree()。pvPortMalloc()和标准C库的malloc()有同样的函数原型,vPortFree()和标准C库的free()有同样的函数原型。
pvPortMalloc()和vPortFree()都是公共函数,因此能够被应用代码调用。
FreeRTOS对于pvPortMalloc()和vPortFree()提供了5种实现。FreeRTOS应用程序可以使用其中的一种,或者使用自己的实现。
5种实现分别在heap_1.c,heap_2.c,heap_3.c,heap_4.c和heap_5.c文件中,都存在于文件夹FreeRTOS/Source/portable/MemMang下。
下面来介绍一下其中的一种实现 Heap_1:
小型嵌入式系统通常只在启动调度程序之前创建任务和其他内核对象。在这种情况下,内存只能在应用程序开始执行任何实时功能之前由内核动态分配,并且内存将在应用程序的生命周期内保持不被释放。这意味着所选择的分配方案不需要考虑任何更复杂的内存分配问题,比如确定性和碎片化,而只需要考虑代码大小和简单性等属性。
Heap_1.c实现了一个非常基本的pvPortMalloc()版本,但没有实现vPortFree()。heap_1适用于从不删除任务或其他内核对象的应用程序。
一些禁止使用动态内存分配的关键的商业系统和关键的安全系统也有可能使用heap_1。由于担心出现不确定性、内存碎片化和内存分配失败等问题,关键系统通常禁止动态内存分配,但是Heap_1总是确定性的,并且不能分割内存。
当调用pvPortMalloc()时,heap_1分配方案将简单数组细分为更小的块。这个数组称为FreeRTOS堆。
数组的大小(以字节为单位)由FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE定义设置。以这种方式定义一个大数组会使应用程序看起来消耗大量ram——甚至在从数组中分配任何内存之前也是如此。
每个创建的任务都需要一个任务控制块(TCB)和一个从堆中分配的堆栈。图5演示了heap_1如何在创建任务时细分简单数组。
如上图所示:
A. 在创建任何任务之前显示数组——整个数组是空闲的。
B. 显示创建一个任务后的数组。
C. 显示创建了三个任务之后的数组。