文章目录
摘要
内存管理
Heap_4
在Heap_2的基础上增加了相邻空闲内存合并,解决了碎片问题,时间是不定的。
主要是解决Heap_2的碎片化。在使用Heap_2分配内存的时候,其实就相当于每一个Block都是独立的,并且如果该Block不满足当前分配的空间,就寻找下一个。这就是Heap_2最大的弊端。存在一种可能,大内存被分割以后,由于分割剩下的内存较小,一直得不到利用,并且这种被分割的小内存越来越多,就会导致内存浪费,严重碎片化。

并且我们Free的时候只是Free除去头部的内存,因为头部是不能释放的,释放了就无法管理这一块的内存。
Heap_4解决的就是这个问题,
在Heap_2中使用的大小排序,显然在Heap_4中就不能使用这个办法, 因为大小排序意味着完全打乱了内存,那么Heap_4只能使用按照地址排序这样就能使用进行地址之间的合并,从而解决了内存的碎片化。
Heap_2:
c
/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, xEnd;
Heap_4:
c
/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, *pxEnd = NULL;

Heap_2和Heap_4的稍微细节区别。
初始化结构
c
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/* Ensure the heap starts on a correctly aligned boundary. */
uxAddress = ( size_t ) ucHeap;
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
pucAlignedHeap = ( uint8_t * ) uxAddress;
/* xStart is used to hold a pointer to the first item in the list of free
blocks. The void cast is used to prevent compiler warnings. */
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/* pxEnd is used to mark the end of the list of free blocks and is inserted
at the end of the heap space. */
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/* To start with there is a single free block that is sized to take up the
entire heap space, minus the space taken by pxEnd. */
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* Only one block exists - and it covers the entire usable heap space. */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/* Work out the position of the top bit in a size_t variable. */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

c
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap; 对齐以后实际堆的大小。
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;//所以需要+xTotalHeapSize表示的就是该堆的尾地址。
uxAddress -= xHeapStructSize;//减去结构体大小
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );//对齐操作
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
并且还需要计算实际的堆有多少。
c
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
这个就是为了计算实际的堆大小,也就是去除尾部节点占用的内存、去所有的除字节对齐、去除第一个空闲内存节点结构体占用的大小,得到的就是可以配分配的内存大小。
c
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
表示的是1左移31位。属于是标志位,表示这个内存块是被使用的。只有在free的时候才会清空。
具体操作


还有一个就是针对大小一定是包含头部一共的大小。并且我们传入的大小其实在内部会累加头部大小的,因此不用担心不会累加头部,导致计算错误。
释放函数---关键
在释放的时候,插入Block的时候,一定是需要按照地址的大小顺序插入的,这是关键。其实这也是为什么我们初始化的时候将最后一个链表尾部的节点设置为指针形式是一个道理,就是要利用他的地址,保证我们排序的时候的便利性,相当于始终知道最大的地址是多少。利用空间换取代码的健壮性。

插入以后还需要看Block1、Block2、Block3能否合并。个人认为只需要判断插入的左右能否合并。因为插入只会影响左右能否合并,并不能影响其他部分能否合并。


具体的将这个释放后的内存插入已有的空闲链表,顺便完成合并成操作
第一步:
排序

第二步:
也就是先跟左边的合并, 合并很简单,就是直接在地址的基础上加上Size,如果想加后地址相同那么就是相邻,直接就是合并。
如果不合并也等会处理,接下来先处理和右边是否能合并。
第三步:
判断能否跟右边合并, 思路也是大差不差。

假设合并的Block是:

在合并的时候Block1指向的很关键,不要轻易覆盖。因为在Block3跟Block2合并的时候,全靠Block1->pxNextFreeBlock 找到Block2的地址。

==结合我画的图,看起来就很清晰了这段代码。==
**在操作链表的时候,最好自己配图理解。这样就很便于理解。防止操作错误。**
第四步:
处理第二步的遗漏问题

这就是刚才说的,如果在第二步左边满足合并要求,这样写没有问题。
但是如果不满足合并要求,直接就是直接使用上述代码,就相当于是直接将Block1->pxNextFreeBlock 指向的Block2地址给覆盖掉了。直接指向我们新插入的,那么我们就相当于是没有将Block3->pxNextFreeBlock 给指向Block2这样Block2的地址直接就丢失了。这就是误操作链表带来的后果。
所以在操作链表的时候一定注意的是先保存,在操作。亦或者一定要谨慎覆盖。
注意标志位的使用。标记该内存是否是空闲内存,这样在释放的时候就会看一下,如果已经被释放过一次,就不会被重复释放了。增加健壮性。
Heap_5
在Heap_4基础上增加了分隔的内存块。

初始化不同
c
/* Used by heap_5.c. */
typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;

所以在使用的时候还是应该预先定义这些内存块数组,
HeapRegion_t array[ ] ={
{addr_A, Size_A},
{addr_B, Size_B},
{NULL, 0}, //必须要这样定义,这样才能知道这个数组遍历结束,相当于是一个判断条件。
}
vPortDefineHeapRegions(arry)
具体操作

只不过将这个两个内存进行了链接。
并且在构造的时候一定是前面的小,后面的大,也就是addr_a < addr_b

结束语
如果觉得我的内容对您有帮助,希望不要吝啬您的赞和关注,您的赞和关注是我更新优质内容的最大动力。
专栏介绍
《嵌入式通信协议解析专栏》
《PID算法专栏》
《C语言指针专栏》
《单片机嵌入式软件相关知识》
《FreeRTOS源码理解专栏》
《嵌入式软件分层架构的设计原理与实践验证》
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言,笔者一定知无不言,言无不尽。