FreeRTOS解析:Mem - 内存管理
受博客限制,如果您想获得更好的阅读体验,请前往https://github.com/Nrusher/FreeRTOS-Book或者https://gitee.com/nrush/FreeRTOS-Book下载PDF版本阅读,如果您觉得本文不错也可前往star,以示对作者的鼓励。如发现问题欢迎交流。
FreeRTOS提供了5种不同的内存管理策略以应对不同的应用场景,本章将对这5种不同的内存管理策略的实现进行分析。(本章中部分图未画画出堆上字节对齐处理细节,请自行理解)
heap_1.c
heap_1.c所实现的内存管理方法十分简单,其可以使用pvPortMalloc()函数来申请内存,一旦申请成功了,便无法被释放。其实现大致可以用一句话概括,**在堆空间剩余时,按需分割出内存,并记录已用的内存大小。**heap_1.c使用的内存管理算法虽然简单,但对于许多嵌入式应用场景是适用且有效的。
在heap_1.c中,堆被定义为一个大数组,并使用变量xNextFreeByte记录已用内存大小
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
static size_t xNextFreeByte = ( size_t ) 0;
核心代码pvPortMalloc()函数实现如下
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
/* 如果对齐字节数不为1,则对请求的字节数做调整 */
#if( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
#endif
/* 挂起任务 */
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL )
{
/* 确保堆的起始地址是按字节对齐的(与操作可能会使地址值减小,因此使用&ucHeap[portBYTE_ALIGNMENT]作为起始地址) */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
/* 检查是否有足够的空间分配 */
if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )/* 防止数值溢出 */
{
/* 返回首地址 */
pvReturn = pucAlignedHeap + xNextFreeByte;
/* 记录已分配空间大小 */
xNextFreeByte += xWantedSize;
}
}
/* 恢复任务 */
( void ) xTaskResumeAll();
return pvReturn;
}
需要注意的是,并未使用configTOTAL_HEAP_SIZE代表堆大小,而是用configADJUSTED_HEAP_SIZE表示堆大小,configADJUSTED_HEAP_SIZE定义如下
#define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
这里简单粗暴的丢弃掉了对齐字节数个字节,以此来表示堆的起始地址对齐的操作中损失的字节数(最多不会损失掉对齐字节数个字节)。个人认为这里可以改进一下,节省使用内存,既计算出对齐字节操作具体损失的字节数计入后续运算中(其实也节省不了多少内存)。
heap_2.c
与heap_1.c不同,heap_2.c允许使用vPortFree()函数来释放申请的内存,其算法原理是将空闲堆分为若干个大小不等的内存块,并将其按大小排序,使用单向链表连接起来,申请内存时,便从这些链表中寻找最小可满足申请需求的内存块进行分配。分配过程分为两步,首先将原先的内存块的链表项从链表中删除,其次是对当前内存块进行分割,将多余申请数的那部分内存变为新的链表项重新插入到链表中。释放过程则更为简单,只需要将释放的内存块重新插入到链表中即可。
首先分析heap_2.c是如何表示一个内存块并将内存块加入链表中的。内存块的链表项定义如下
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /* 指向下一个未被使用的内存块 */
size_t xBlockSize