本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿。
轻量级操作系统FreeRTOS的内存管理机制(二)中讲到,heap2.c的内存管理机制会导致内存碎片的问题,系统运行久后会出现无法分配大块内存的情况,heap4.c中的管理机制提供了解决方法,它是在heap2.c的基础上添加了地址相邻空闲块间合并的功能,而heap5.c是对heap4.c的进一步扩展,它能够支持多块不连续分布的RAM空间作为堆使用,本篇将对heap4.c、heap5.c中的管理机制进行分析。
一、heap4.c
1.重要结构体
2.重要变量
xBlockAllocatedBit在堆初始化时被赋值为:
即最高位为1,在分配内存的时候xBlockSize将和置位变量xBlockAllocatedBit相与,使得xBlockSize最高位置位,表示改内存块属于应用程序;在回收内存时,通过xBlockSize &= ~xBlockAllocatedBit操作,将xBlockSize 最高位清零表示该内存空间未分配,xBlockSize 最高位可用于判断该内存当前是应用程序还是操作系统管理。
3.堆初始化
prvHeapInit(void)函数用于初始化堆空间,在第一次内存分配前需要被调用,主要完成了几个工作:
1)根据字节对齐划分有效的可分配空间;
2)初始化链表结构;
初始化后的链表结构和内存空间:
在heap4.c方案中,链表结点是按照空闲块地址由低到高排序的。上图中,xStart是表头结点,pEnd是表尾结点指针,链表的表尾结点在堆中进行分配。
4.内存分配机制
heap4.c内存分配策略:空闲块通过链表结构进行管理,当需要分配一块内存时,遍历链表,找到第一块够大的空闲块进行分配,从链表中移除出来,同时检查这块空间是否过大,如果过大就将空闲块进行切割,然后将剩下部分重新用BlockLink_t描述并插入到链表中。
和heap2.c有所不同的是,当找到了合适的空闲块后,BlockLink_t中的成员xBlockSize的最高位会置位为1,以表示该空间属于应用程序:
因此,请求分配的空间大小xWantedSize不能大到最高位为1。
5.内存回收
在内存回收过程中,vPortFree函数会读取pv所指空间的BlockLink_t结构体,首先判断成员xBlockSize最高位是否为1(即该空间原属于应用程序),如果满足才进行内存回收。
在heap4.c中最大的特点是在回收内存时添加了相邻空闲块的合并的算法,函数prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )将空闲块插入链表中(由vPortFree()调用),和heap2.c对比,所不同的有两点:
1)heap4.c的链表是按照地址低到高排序的,heap2.c的链表是根据空闲块大小排序的;
2)在插入链表过程中,判断插入位置的前后结点的空闲块是否和回收的内存空间在地址上连续,如果是,就进行合并。
以下图例是一个合并的过程:
二、heap5.c
heap5.c是对heap4.c的进一步拓展,heap5.c能够支持多块不连续的RAM空间作为内存分配空间,内存分配策略和回收机制和heap4.c一样。
heap5.c中定义了一个重要结构体:
这个结构体描述用作堆空间的一块内存的信息,记录了该内存块的起始地址和大小。多块不连续的内存使用一个HeapRegion_t[]数组记录,并且约定数组以{NULL, 0}元素结尾,其作为vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )函数的参数,进行堆初始化。
在FreeRTOS中的WIN32-MSVC示例是采用heap5.c的内存分配策略,HeapRegion_t[]示例如下:
经过初始化后的堆空间和链表结构如下图所示:
vPortDefineHeapRegions()函数将HeapRegion_t[]数组中不连续的RAM空间连接起来:通过在一块内存末尾存储一个BlockLink_t结构体,其pxNextBlock成员指向下一块内存中的第一块空闲块以建立起连接。
heap5.c较heap4.c而言主要增加了这部分功能,保留了heap4.c中的内存分配、内存回收的思想。
关注微信公众号:嵌入式企鹅圈,获得上百篇物联网原创技术分享!