在介绍本文之前,向大家推荐个非常容易入门的人工智能学习网站,建议点击收藏❤️
内存管理对应用程序和操作系统来说都非常重要。现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关。FreeRTOS官方提供了5种内存管理解决方案,这里的内存一般情况下指的是RAM,主要是针对使用者可以针对不同的应用场景使用一种或同时使用多种。在同时使用多种内存管理方案时,可以将原生的API重命名,然后分别初始化,初始化的时候注意要链接不同的内存块。学习RTOS内存管理的时候要先搞清楚它的字节对齐是怎么实现的,这会对看源码有很大帮助。
在系统中定义个大数组,内存管理实际就是在这个数组里面操作。这种内存管理方案不会对已申请的内存进行释放。
如上图vPortFree()里面是空操作,这种内存管理方案不对已申请的内存做释放。vPortInitialiseBlocks()是初始化,只对一个剩余内存变量置零,xPortGetFreeHeapSize()是获取剩余内存。下面通过代码注释的形式分析pvPortMalloc()。
void *pvPortMalloc(size_t xWantedSize)
{//xWantedSize:需要从大数组中获取的内存大小
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
/* 确保字节对齐,可以提高CPU访问速度 */
#if (portBYTE_ALIGNMENT != 1)
{
if (xWantedSize & portBYTE_ALIGNMENT_MASK)
{
/* Byte alignment required. */
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));
}
}
#endif
vTaskSuspendAll();
{
if (pucAlignedHeap == NULL)
{
/* 确保大数组的起始地址的是对齐的,有些是2字节对齐或4字节或8字节或其他,视不同的编译器而定. */
pucAlignedHeap = (uint8_t *)(((portPOINTER_SIZE_TYPE)&ucHeap[portBYTE_ALIGNMENT]) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
}
/* 检查是否有足够的空间 */
if (((xNextFreeByte + xWantedSize) < configADJUSTED_HEAP_SIZE) &&
((xNextFreeByte + xWantedSize) > xNextFreeByte)) /* Check for overflow. */
{
/* 申请成功后返回申请到的内存起始地址,以及对剩余内存变量做修改. */
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC(pvReturn, xWantedSize);
}
(void)xTaskResumeAll();
#if (configUSE_MALLOC_FAILED_HOOK == 1)
{
if (pvReturn == NULL)
{//内存申请失败回调函数
extern void vApplicationMallocFailedHook(void);
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
这种内存管理方案在heap_1的基础上新增了内存释放的功能,通过链表来现的。
static void prvHeapInit(void)
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = (uint8_t *)(((portPOINTER_SIZE_TYPE)&ucHeap[portBYTE_ALIGNMENT]) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
/* 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;
/* xEnd is used to mark the end of the list of free blocks. */
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;
/* To start with there is a single free block that is sized to take up the
entire heap space. */
pxFirstFreeBlock = (void *)pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
void *pvPortMalloc(size_t xWantedSize)
{
//如果是第一次使用本接口要先对该内存管理方案进行初始化
//每次申请内存的大小需要加上链表节点大小
//空闲内存管理链表的大块内存可能变成小内存
//返回可使用的内存的起始地址
}
传入的参数往前退一个链表节点大小才能获取到每个节点的信息。
void vPortFree(void *pv)
{
uint8_t *puc = (uint8_t *)pv;
BlockLink_t *pxLink;
if (pv != NULL)
{
/* The memory being freed will have an BlockLink_t structure immediately
before it. */
puc -= heapSTRUCT_SIZE;
/* This unexpected casting is to keep some compilers from issuing
byte alignment warnings. */
pxLink = (void *)puc;
vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
prvInsertBlockIntoFreeList(((BlockLink_t *)pxLink));
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE(pv, pxLink->xBlockSize);
}
(void)xTaskResumeAll();
}
}
获取剩余内存大小。
这种内存管理方案仅仅是对系统的malloc和free进行封装,做了线程保护。不行要自己分配大数组。
该内存管理算法不会产生内存碎片,在内存回收的时候增加了附近的小内存合并的操作。
内存回收的时候,是按地址大小去遍历的:
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
需要对变量"xBlockAllocatedBit"作特别说明,它的存在使得链表节点头中的xBlockSize包含两种意思,一个是当前内存块的大小,另一个是最高位表示该内存块是否在使用中。
该内存管理算法在heap_4.c的基础上增加了非连续的多个内存块的使用,体现在初始化上面:
typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;
HeapRegion_t xHeapRegions[] =
{
{(uint8_t *)0x80000000UL, 0x10000}, // << Defines a block of 0x10000 bytes starting at address 0x80000000
{(uint8_t *)0x90000000UL, 0xa0000}, // << Defines a block of 0xa0000 bytes starting at address of 0x90000000
{NULL, 0} // Terminates the array.
};
vPortDefineHeapRegions(xHeapRegions);
FreeRTOS将内核语内存管理分开实现,内核只规定了必要的内存管理函数原型,但不关心这些内存管理函数是如何实现的。这样的方案大大提高了系统的灵活性:不同的应用场景使用不同的内存管理算法。比如对于安全型的嵌入式系统,一般不允许动态内存分配,大都是通过静态分配的。在满足需要的前提下,系统越简单,做得越安全。