FreeRTOS 的五种内存管理策略

在嵌入式嵌入式系统设计中,内存分配应根据所设计系统的特点来选择使用动态内存分配还是静态内存分配策略, 一些可靠性要求很高的系统应使用静态内存分配,而一般普通的业务使用动态内存策略即可,这样便可以提高内存的使用效率。
Freertos 将内核和内存管理分开实现, 内核仅规定了必要的内核函数原型而不必关心这些内存管理函数是如何实现的,操作系统提供了多种内存分配策略,但在接口上是统一的(申请内存均调用pvPortMalloc(),释放内存均调用pvPortFree()),用户可以根据具体应用场景的需要来选择不同的内存分配策略。
当内核请求内存时,其调用pvPortMalloc()而不是直接调用malloc();当释放内存时,调用vPortFree()而不是直接调用free()。pvPortMalloc()具有与malloc()相同的函数原型;vPortFree()也具有与free()相同的函数原型。
FreeRTOS提供了五种内存管理策略,对应三个源文件:heap_1.c,heap_2.c,heap_3.c,heap_4.c ,heap_5.c——这五个文件都放在目录FreeRTOS\Source\Portable\MemMang 中.

一、 Heap_1.c

Heap_1.c 管理方案是五种管理方案中最简单的一个,只能进行内存的申请而不能进行内存的释放,实现了一个基本的pvPortMalloc()函数来申请内存,而且没有实现vPortFree()的内存释放函数。由于不允许删除内存,也就不会产生内存碎片问题,并且申请内存的时间是确定的,适合于对安全要求高的嵌入式系统。
但其缺点也很明显,那就是内存利用率不高,需考虑内存上限问题。
这种内存管理方案常用于从不删除任务,队列或者信号量等的应用程序,实际上大多数的嵌入式系统都符合这个条件,因此使用最为广泛。
Heap_1.c分配方案将FreeRTOS 的内存堆空间看作一个简单的数组, 数组的总大小(字节为单位)由configTOTAL_HEAP_SIZE来定义并使用下面这两个静态变量对系统管理的内存进行跟踪分配:
static size_t xNextFreeByte = (size_t)0;
static uint8_t *pucAlignedHead = NULL;
变量xNextFreeByte用来定位下一个空闲的内存堆位置,每次申请内存成功后,都会增加申请内存的字节数,其作用就是记录已分配的内存大小;变量pucAlignedHead 是一个指向对齐后的内存堆起始地址,以数组作为内存堆,其起始地址是要求对齐的,configADJUSTED_HEAP_SIZE表示的就是地址对齐后系统真正可管理的内存空间大小。
当调用pvPortMalloc() 申请一块用户指定大小的内存空间时,当系统管理的剩余可用内存空间满足用户需要的大小时,便能够申请成功,并返回所申请的内存空间的起始地址,下图描述了Heap_1.c内存分配方案内存初始化后和申请内存后的内存空间分布情况:
Heap_1.c申请前后内存空间示意图

二、 Heap_2.c

Heap_2.c 也是分配方案是将FreeRTOS 的内存堆空间看作一个简单的数组不同于heap_1 的是,heap_2 采用了一个最佳匹配算法来分配内存,并且支持内存释放,但它不能把相邻的小空闲内存块合并起来,在每次申请的内存大小不一致的情况下,便会产生内存碎片问题。
这种内存管理方案适用于申请内存大小是固定的(如内核中的任务,队列,信号量)并会反复删除所申请内存操作的应用程序,不可用于内存申请时随机大小的应用程序,具有不确定(每次调用函数的执行时间是不确定的),但是该方案比大多数标准库实现的malloc()与free()具有更高的效率。
Heap_2.c分配方案在内存堆初始化时的操作和与Heap_1.c方案是一样的,都是在内存中开辟静态数组作为堆空间,不同的是,该方案采用链表的数据结构来记录空闲内存块,将所有的空闲内存块组成一个空闲内存块链表(该链表按内存块的大小进行升序排序),空闲内存块链表的结构BlockLink_t如下:
typedef struct BLOCK_LINK{
struct BLOCK_LINK *pxNextFreeBlock;
size_t XblockSize;
}BlockLink_t;
结构体成员变量pxNextFreeBlock是指向下一个空闲内存块的指针;而成员变量XblockSize则用来记录申请的内存块的大小(包含链表结构体的大小headSTRUCT_SIZE),系统使用两个这样类型的静态局部变量xStart xEnd 来标识空闲内存块链表的起始位置和终止位置。下图描述了Heap_2.c内存分配方案内存初始化完成时的内存空间分布情况
Heap_2. 内存初始化内存空间示意图

该分配方案调用pvPortMalloc()申请内存时,系统会从空闲内存块链表头开始遍历,查到符合用户所申请的大小的内存块,找到后,便会返回该内存块偏移headSTRUCT_SIZE 字节后的地址;
对于申请成功的内存块,系统还会判断这块内存是否还有剩余,若剩余内存大于链表结构体的大小,则会将剩余内存进行分割成一个小的可用空闲内存内存块,并按其按内存大小插入到空闲内存块链表中,这样可以充分的利用内存空间,但如果这种情况频繁发生,大的内存块分割会成两个甚至更多个小的内存块,就会导致每个空闲块都很小,最终在申请大内存块时由于没有连续可用的大内存导致申请失败。
下图描述了Heap_2.c内存申请内存后及释放内存后的内存空间分布情况:
Heap_2.c申请一次内存后内存空间示意图

Heap_2.释放内存后内存空间示意图
对于Heap_2的内存释放,需向内存释放函数pvPortFree()传入要释放的内存地址,系统会根据地址找到对应的内存链表节点,并将该内存链表节点按内存块的大小插入到空闲内存块链表节中,把内存规划给系统,并更新未分配内存的大小。

三、 Heap_3.c

Heap_2.c分配方案只是简单封装了标准库函数中的malloc()和free(),在操作内存前挂起调度器,完成后再恢复,内存申请和释放函数实现很简单。

四、 Heap_4.c

Heap_4.c分配方案与Heap_1.c方案一样,也采用最佳匹配算法来实现动态的分配内存,不同的是该方案提供了一种合并算法,能够把相邻的小空闲内存块合并成一个更大的内存块,这样便减少内存碎片问题。
这种内存管理方案适用于申请内存大小是随机的(如内核中的任务,队列,信号量)并会反复删除所申请内存操作的应用程序,同样具有不确定(每次调用函数的执行时间是不确定的),效率也远高于标准库函数中的malloc()和free()。
Heap_4. 内存初始化内存空间示意图

上图描述了Heap_4.c内存分配方案内存初始化完成时的内存空间分布情况
Heap_4.c分配方案在内存堆初始化时的操作和与Heap_4.c方案基本是一样的,都是在内存中开辟静态数组作为堆空间,并采用链表的数据结构来管理空闲内存块,同样也使用两个BlockLink_t这样类型的静态局部变量xStart、 pxEnd 来标识空闲内存块链表的起始位置和终止位置, 不同的是的,Heap_2.c内存管理方案直接使用静态局部变量xEnd表示链表的尾部, 而Heap_4.c方案的链表尾部则是保存在内存堆空间最后的位置,并使用静态局部变量pxEnd指向该区域。
另外Heap_2.c方案管理内存的空闲块链表是按内存块起始地址的大小进行升序排序,而Heap_4.c方案的空闲块链表是内存块起始地址大小进行升序排序的,这也是为了配合方案中的合并算法而进行的改变。

下面图片分别描述了Heap_4.c申请三次内存后、释放一次及释放两次内存内存后的内存空间分布情况:
Heap_4.c申请三次内存后内存空间示意图
Heap_4.c方案的内存申请函数操作和与Heap_4.c 方案的原理基本一致,同样是从空闲块内存链表表头开始遍历查找适合适合用户所申请大小的内存块,找到后,便会返回该内存块偏移headSTRUCT_SIZE 字节后的地址;
同样对于申请成功的内存块,系统也会判断这块内存是否还有剩余,若剩余内存大于链表结构体的大小,则会将剩余内存进行分割成一个小的可用空闲内存内存块,并将其按内存块起始地址的大小插入到空闲内存块链表中,并且在该过程中还会执行合并算法,判断相邻的空闲内存块的地址是否连续,如果满足条件,则会将其合并成一个大的空闲内存块,这样可大大减少的内存碎片的产生。
 Heap_4.c释放一次内存后内存空间示意图
Heap_4.c释放两次内存后内存空间示意图
对于Heap_4的内存释放,同样是需向内存释放函数pvPortFree()传入要释放的内存地址,偏移headSTRUCT_SIZE 字节便可找到对应的内存链表节点,然后将该内存链表节点按内存块起始地址的大小插入到空闲内存块链表节中,把内存规划给系统,在这个过程中也会执行合并算法,在满足条件的情况下会把相邻的小空闲内存块合并成一个更大的内存块。

五、Heap_5.c

Heap_5.c分配方案与Heap_4.c方案一样,也采用最佳匹配算法和和合并算法来实现动态的分配内存,不同的是该方案允许内存堆跨越非连续的内存区域,即支持在地址不连续的内存堆中实现动态内存分配,不再只局限于片内的RAM,还可同时管理扩展的片外SDRAM的内存。
Heap_5.c方案在内存堆初始化时的操作和与Heap_4.c方案大体是一致的,不同的是,需要为所包含的每个内存堆进行初始化,提前定义好每个内存堆区域的起始地址和大小, 内存堆采用HeapRegion_t 结构体类型数组来管理,该结构体定义如下:
type struct HeadRegion {
uint8_t *pucStartAddress; //内存堆起始地址

size_t XsizeInBytes; //内存堆大小
} HeapRegion_t;
在申请和释放内存前,必须先完成内存初始化,下图描述了Heap_2.c内存分配方案内存初始化完成时的内存空间分布情况:
Heap_2. 内存初始化内存空间示意图

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值