【FreeRTOS】堆内存管理
动态内存分配及其与FreeRTOS的相关性
为了使FreeRTOS更易用,内核对象(如任务、队列、信号量、事件组)不在编译期静态分配,而是在运行时动态分配,FreeRTOS在内核对象创建时分配RAM,删除内核对象时释放RAM。
这种策略降低了设计难度,更简单的API,最小化内存占用。动态内存分配时C编程的概念,而非特定于FreeRTOS或多任务的概念,只是FreeRTOS的内核对象是动态分配的,一般编译器提
供的动态内存分配方案并不总是适用于实时应用。
可以使用标准C库函数malloc和free进行内存分配,但他们并不总是适用的,主要原因如下:
- 在小型嵌入式系统上并不总是可用
- 他们实现通常很大,占据昂贵的代码空间
- 他们几乎不是线程安全的
- 他们不是确定性的,执行函数所花费的时间因调用而异
- 导致内存碎片化严重
- 导致链接配置更复杂
- 无法调试
动态内存分配选项
早期的FreeRTOS使用内存池分配方案,不同大小的内存池是在编译期预先分配的,尽管这是一个实时系统的通用方案,但它不能有效的使用RAM,在非常小的嵌入式系统无法使用,因此被放弃了。
FreeRTOS现在将内存分配视作可移植层的一部分而非核心代码,这是因为不同嵌入式系统对动态内存分配和时间要求不同,因此单一动态内存分配算法值适用某一种应用,所以将动态内存分配代码
从核心代码中删除,使得使用者可以在特定的应用使用合适的动态内存分配算法。
FreeRTOS分配内存使用接口pvPortMalloc而非malloc,释放内存使用vPortFree替代free。
FreeRTOS提供5种内存分配方案,接口统一使用pvPortMalloc和vPortFree,用户可以使用者5种方案,也可以自己实现。这5种内存分配方案实现在目录FreeRTOS/Source/portable/MemMang下,
命名分别是heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c.
内存分配方案介绍
Heap_1
小型专用嵌入式系统在调度器运行前只创建任务和内核对象是很常见的,也就是说在应用程序执行前进行动态内存分配,而后一直保持直到应用程序生命周期结束,意味着此方案无需考虑复杂的内存
分配问题,比如确定性和碎片化,只需考虑代码大小即可。
heap_1.c实现基础版内存分配接口pvPortMalloc,未实现内存释放接口vPortFree,使用该算法的应用程序永不删除任务、内核对象。
一些商用型安全关键的系统禁止使用动态内存分配,故选用heap_1方案,以避免内存分配的不确定性。
pvPortMalloc使用heap_1方案,将从数组中分配一个小内存块,这个数组就是FreeRTOS堆内存,数组大小由宏configTOTAL_HEAP_SIZE配置,创建的每个任务都有一个任务控制块和任务栈,图
Figure-5演示了任务创建时聂村的分配过程:

- A显示任务创建前整个内存数组是空的
- B显示创建1个任务后的内存数组布局
- C显示创建3个任务后的内存数组布局
Heap_2
为了向后兼容,heap_2在FreeRTOS发行版中被保留,但不建议在新设计中使用它,可以使用heap_4替代,因为heap_4提供了增强版的功能。heap_2使用最佳匹配算法进行内存分配和释放。
最佳匹配算法确保pvPortMalloc使用大小最接近请求字节数的空闲内存块。
示例考虑以下场景:
- 堆内存包含3个空闲内存块,分别是5Byte、25Byte、100Byte
pvPortMalloc请求分配20Bytes内存
与请求20Byte内存大小最接近的是25Byte空闲内存块,pvPortMalloc将25Byte内存块分成20Byte和5Byte,并返回20Byte内存块指针,剩余的5Byte以供将来分配使用。
heap_2方案不会将相邻的空闲内存块合并为更大的空闲内存块,所以更容易产生内存碎片。当然每次分配的内存大小与随后释放的内存大小一致,则就不会存在内存碎片问题。
heap_2方案适合重复创建和删除任务的应用,前提是分配给创建任务的堆栈大小不改变。

图Figure-6演示了最佳匹配算法的工作过程,任务创建、删除、再次创建
- A显示创建了3个任务,数组顶部还剩余大块空闲内存
- B显示删除了1个任务,当前多出了2块小内存,即之前创建任务的任务控制块和任务栈
- C显示新创建1个任务,创建任务调用2次
pvPortMalloc,分别是分配任务控制块和任务栈
每个任务控制大小都一样,所以最佳匹配算法确保在先前释放的任务控制块内存位置重复分配新任务的任务控制块,新创建任务的任务栈与之前释放的任务栈大小相同,所以最佳匹配算法仍在原位置分配任务栈,
数组顶部的未分配的内存自始至终都未触碰
heap_2方案不是确定性的,但比标准库函数malloc和malloc执行快
Heap_3
heap_3方案使用标准库函数malloc和free,堆大小在链接文件中分配,宏configTOTAL_HEAP_SIZE配置的修改无影响。
heap_3方案通过挂起调度器实现函数malloc和free的线程安全。
Heap_4
跟heap_1、heap_2一样,heap_4也是从数组中分出小块内存,堆内存大小也是宏configTOTAL_HEAP_SIZE配置的。
heap_4使用第一次匹配算法进行内存分配,相比heap_2,heap_4会合并相邻的空闲内存块,将内存碎片风险最小化。第一次匹配算法确保pvPortMalloc分配内存时,总是找到第一个可以满足请求内存大小的
空闲内存块。
示例考虑如下场景:
- 堆内存包含3个空闲内存块,分别是5Byte、200Byte、100Byte的顺序
pvPortMalloc请求分配20Bytes内存
第一次匹配算法就会找到200Byte内存块分配内存,pvPortMalloc将200Byte内存块分成20Byte和180Byte两个内存块,并返回20Byte内存块指针,新的180Byte内存供后续分配使用。
heap_4方案能够合并相邻空闲内存块,最小化内存碎片化风险,并重复分配释放不同大小的内存。

图Figure-7演示了在内存分配分配释放时heap_4第一次匹配算法和合并是如何工作的:
- A显示创建3个任务后的内存数组布局
- B显示删除1个任务后的内存数组布局,空闲出了删除任务的任务控制块和任务栈空间,并合并为更大的空闲内存块
- C显示创建1个FreeRTOS队列,因heap_4的第一次匹配算法机制,
pvPortMalloc将队列所需内存分配至刚删除任务释放出的位置,但未占用完有剩余 - D显示用户代码直接调用
pvPortMalloc分配内存,按heap_4的第一次匹配算法机制,之前释放位置重新分配后还有剩余 - E显示刚创建的队列被删除了,空闲内存分布在用户申请的内存两侧
- F显示用户代码申请的内存释放了,分散的内存块又合并成大的空闲内存块
heap_4方案不是确定性的,但比标准库函数malloc和free执行快
heap_4方案内存数组起始地址设置:
有时会指定堆内存的位置,任务栈是从堆内存分配的,所以为了快速会将堆内存放在内部RAM而非外部RAM,默认情况下heap_4方案的堆内存是在链接文件中配置的,但若开启了宏
configAPPLICATION_ALLOCATED_HEAP,那内存数组就由应用程序提供,命名是uint8_t ucHeap,大小由宏configTOTAL_HEAP_SIZE配置
将对内存放置在指定位置的语法取决于具体编译器:
- GCC编译器,将堆内存地址指定到段.my_heap
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ( ( section( ".my_heap" ) ) );
- IAR编译器,将堆内存地址指定到地址0x20000000
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] @ 0x20000000;
Heap_5
heap_5使用的内存分配释放算法与heap_4一样,但不一样的是heap_5可以在多段独立的内存空间分配,而heap_4只能用于单个内存空间。heap_5方案使用场景是,运行FreeRTOS的系统提供的堆内存并非连续的一片内存。
在编写时,heap_5是唯一一个必须在调用pvPortMalloc()之前显式初始化的内存分配方案。Heap_5使用vPortDefineHeapRegions() API函数初始化。当使用heap_5时,必须在创建任何内核对象(任务、队列、信号量等)
之前调用vPortDefineHeapRegions()。
vPortDefineHeapRegions用于指定每个单独内存区域的起始地址和大小,这些内存区域共同构成了heap_5所使用的总内存。
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
每个单独的内存区域由HeapRegion_t类型的结构描述。所有可用内存区域的描述作为HeapRegion_t结构的数组传递给vPortDefineHeapRegions()。
typedef struct HeapRegion
{
/* The start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
/* The size of the block of memory in bytes. */
size_t xSizeInBytes;
} HeapRegion_t;
示例说明heap_5的多段内存使用过程

在构建项目时,构建过程的链接阶段会为每个变量分配一个RAM地址。可由链接器使用的RAM通常由链接器配置文件(如链接器脚本)描述。在Figure-8 B中,假定链接器脚本包含RAM1上的信息,但不包含RAM2或RAM3上
的信息。因此链接器在RAM1中放置了变量,只留下RAM1地址0x0001nnnn以上的部分可以通过heap_5使用。0x0001nnnn的实际值取决于被链接的应用程序中包含的所有变量的大小。链接器让所有RAM2和RAM3都未使用,
让整个RAM2和RAM3都可以被heap_5使用。
/* Define the start address and size of the three RAM regions. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Create an array of HeapRegion_t definitions, with an index for each of the three
* RAM regions, and terminating the array with a NULL address. The HeapRegion_t
* structures must appear in start address order, with the structure that contains the
* lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{
{ RAM1_START_ADDRESS, RAM1_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* Marks the end of the array. */
};
int main( void )
{
/* Initialize heap_5. */
vPortDefineHeapRegions( xHeapRegions );
/* Add application code here. */
}
如果使用上述所示的代码,分配给地址0x0001nnnn下的heap_5的RAM将与用于保存变量的RAM重叠。为了避免这种情况,xHeapRegions[]数组中的第一个HeapRegion_t结构体可以使用0x0001nnnn的起始地址,而不是0x00010000的起始地址。
但是,这不是一个推荐的解决方案,因为:
- 起始地址不易确定
- 链接脚本文件会不定期修改,导致结构
HeapRegion_t起始地址也要不停修改 - 如果链接器使用的RAM和heap_5使用的RAM重叠,构建工具将不知道,因此不能警告应用程序编写器
为解决这种场景,代码实现如下:
第一块内存使用静态数组ucHeap,它是分配在链接文件指定的RAM1中,结构HeapRegion_t起始地址使用ucHeap,所以它将是heap_5管理内存的一部分,这样就避免了频繁的修改,如图Figure-8 C所示
/* Define the start address and size of the two RAM regions not used by the
linker. */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Declare an array that will be part of the heap used by heap_5. The array will be
placed in RAM1 by the linker. */
#define RAM1_HEAP_SIZE ( 30 * 1024 )
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
/* Create an array of HeapRegion_t definitions. Whereas in Listing 6 the first entry
* described all of RAM1, so heap_5 will have used all of RAM1, this time the first
* entry only describes the ucHeap array, so heap_5 will only use the part of RAM1 that
* contains the ucHeap array. The HeapRegion_t structures must still appear in start
* address order, with the structure that contains the lowest start address appearing
* first. */
const HeapRegion_t xHeapRegions[] =
{
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* Marks the end of the array. */
};
这种技术的优势是:
- 不必写死内存起始地址
- 结构
HeapRegion_t中的起始地址是通过链接文件自动设置的,省去了频繁修改的烦恼 - heap_5管理的内存与链接文件分配的内存不可能重叠
ucHeap过大则应用程序link失败
堆内存相关的实用函数
- 获取空闲内存的大小,可用于优化堆内存大小
size_t xPortGetFreeHeapSize( void );
- 获取FreeRTOS应用程序自开始执行以来堆中存在的最小未分配字节数,可用于堆内存溢出检查
size_t xPortGetMinimumEverFreeHeapSize( void );
- 内存分配失败后调用的钩子函数,需要设置宏configUSE_MALLOC_FAILED_HOOK,可用于代码鲁棒性检查
void vApplicationMallocFailedHook( void );
【FreeRTOS】堆内存管理的更多相关文章
- FreeRTOS 动态内存管理
以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...
- Linux堆内存管理深入分析(下)
Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...
- Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...
- Linux堆内存管理深入分析
(上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...
- C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针
C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针 (1)开辟的内存没有释放,造成内存泄露 (2)野指针被使用或释放 (3)非法释放指针 (1)开辟的内存没有释放.造成内存泄露,以下的样 ...
- 轻量级操作系统FreeRTOS的内存管理机制(一)
本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 近几年来,FreeRTOS在嵌入式操作系统排行榜中一直位居前列,作为开源的嵌入式操作系统之一,它支持许多不同架构的处理器以及多种编译 ...
- Linux堆内存管理深入分析 (上半部)【转】
转自:http://www.cnblogs.com/alisecurity/p/5486458.html Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来 ...
- FreeRTOS的内存管理
FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的.其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的. FreeRTOS也允许你自己实现内 ...
- Linux C 堆内存管理函数malloc()、calloc()、realloc()、free()详解
C 编程中,经常需要操作的内存可分为下面几个类别: 堆栈区(stack):由编译器自动分配与释放,存放函数的参数值,局部变量,临时变量等等,它们获取的方式都是由编译器自动执行的 堆区(heap):一般 ...
- 栈 & 堆 |--> 内存管理
内存管理: 栈区 [stack]:由编译器自动分配并释放,一般存放函数的参数值,局部变量等 堆区 [heap]:由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 全局区(静态区) ...
随机推荐
- spring-mvc 系列:域对象共享数据
目录 一.使用ServletAPI向request域对象共享数据 二.使用ModelAndView向request域对象共享数据 三.使用Model向request域对象共享数据 四.使用Map向re ...
- C++篇:第十二章_文件及IO_知识点大全
C++篇为本人学C++时所做笔记(特别是疑难杂点),全是硬货,虽然看着枯燥但会让你收益颇丰,可用作学习C++的一大利器 十二.文件及IO 当在输入输出流中使用控制符进行格式控制时,需在程序中加入头文件 ...
- 云小课|MRS基础原理之CarbonData入门
阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:CarbonDat ...
- 不信谣不传谣,亲自动手验证ModelBox推理是否真的“高性能”
摘要:"高性能推理"是ModelBox宣传的主要特性之一,不信谣不传谣的我决定通过原生API和ModelBox实现相同案例进行对比,看一下ModelBox推理是否真的"高 ...
- 实践解析可视化开发平台FlinkSever优势
摘要:华为Flink可视化开发平台FlinkServer作为自研服务,能够提供比原生flinksql接口更强的企业级特性,比如任务的集中管理,可视化开发,多数据源配置等. 本文分享自华为云社区< ...
- JavaScript继承的实现方式:原型语言对象继承对象原理剖析
面向对象编程:继承.封装.多态. 对象的继承:A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 在经典的面向对象语言中,您可能倾向于定义类对象,然后您 ...
- linux 账号管理 添加用户与修改用户权限与密码
添加用户 useradd zhoulujun 修改用户密码 passwd zhoulujun 修改用户组 usermod -g root zhoulujun 记住这个几个就够了--
- HBuilderX获取iOS证书的打包步骤
简介: 目前app开发,很多企业都用H5框架来开发,而uniapp又是这些h5框架里面最成熟的,因此hbuilderx就成为了开发者的首选.然而,打包APP是需要证书的,那么这个证书又是如何获得呢? ...
- App Store上架流程/苹果app发布流程
第一步:拥有自己的苹果开发者账号: 开发账号分为两类:99美元(发布App Store用的,也就是上架苹果商店用这个):299美元(企业授信证书,不用上架appstore 亦可使用.弊端:证书容易被封 ...
- compilation.templatesPlugin is not a function
ERROR Failed to compile with 1 error TypeError: compilation.templatesPlugin is not a function - SetV ...