内存管理

来源:互联网 发布:如何制作mac os启动盘 编辑:程序博客网 时间:2024/06/05 00:48

在单片机编程时使用动态内存管理可以有效节约空间,一般的嵌入式系统(如UCOS,FreeRTOS),网络协议栈(如Lwip)都实现了自己的内存管理算法。这篇文章主要是分析了FreeRTOS的heap4的内存管理思路,然后作者按照这个思路实现了这种内存管理算法。

内存管理实现的基本思路就是创建一个很大的数组,然后每次申请内存,就从未使用的空间找到足够大的空间返回给申请者,然后对这些空间进行标记,当内存表中已经找不到足够大的连续的空间返回就申请失败。释放空间则是将这些标记去除,让它变成未使用的空间。

heap4主要通过一个结构体来实现内存的管理

#define BLOCK_USE_FLAG 0x80000000typedef struct BLOCK_T{   struct BLOCK_T *nextFreeBlock;//指向下一个空闲内存块   //记录自己的的空闲字节,包括此结构体所占的8个字节  最高位用于表示此块空间是否被使用   uint32_t blockSize; }BLOCK_T;

初始化代码如下:

static BLOCK_T heapStart,*heapEnd = NULL;uint8_t heapInit(void){    BLOCK_T *firstFreeBlock;    uint8_t *alignHeap;    uint32_t address;    uint32_t totalHeapSize = HEAP_SIZE;    //做一个8字节对齐的处理     address = (uint32_t)heap;    if((address & (uint32_t)0x00000007) != 0)    {        address += 7;        address &= ~(uint32_t)0x00000007;        totalHeapSize -= address - (uint32_t)heap;    }    //得到对齐地址    alignHeap = (uint8_t *) address;    //起始指针记录    heapStart.nextFreeBlock = (void*)alignHeap;    heapStart.blockSize = (uint32_t)0;    //偏移至堆尾    address = (uint32_t)alignHeap + totalHeapSize;    address -= (sizeof(BLOCK_T));    heapEnd = (void*)address;    heapEnd->nextFreeBlock = NULL;    heapEnd->blockSize = 0;    //起始指针赋值 当前堆中唯一空闲内存块    firstFreeBlock = (void*)alignHeap;    firstFreeBlock->blockSize = address - (uint32_t)firstFreeBlock;    firstFreeBlock->nextFreeBlock = heapEnd;    //记录剩余内存数和最小空闲块    freeByteRemaining = firstFreeBlock->blockSize;    minFreeByteRemaining = firstFreeBlock->blockSize;    return True;}
  1. 首先获取申请的数组的首地址,然后对其进行一个8字节对齐的处理。为什么是8字节呢,因为刚好结构体总共占了8个字节,这样使用方便。
  2. 头指针heapStart指向字节对齐的数组首地址,以后这个指针会当作遍历整个空闲链表的根指针。
  3. 偏移至数组尾部前8个字节,然后在此放置一个heapEnd 的结构体指针。
  4. 在头部放置firstFreeBlock结构体指针,并计算出总共的空闲字节大小。
  5. 记录剩余内存数和最小空闲块

此时整个数组的头部和尾部分别放置一个结构体,根指针指向头部。目前只存在一个空闲块。

接下来讲一下分配内存的思路,程序根据要分配的字节从头部遍历到尾部,找到第一个足够字节大小的空闲块分配给申请者,如果还存在剩余的字节则重新建立一个空闲块,并调整链表指向。

void *myMalloc(uint32_t size){    BLOCK_T *currentBlock,*previousBlock,*newBlock;    void *returnHeap = NULL;    if(size == 0)    {        return returnHeap;    }    //未初始化    if(heapEnd == NULL)    {       heapInit();     }    //申请字节不能大于0x7FFFFFFF - sizeof(BLOCK_T)    if((size & BLOCK_USE_FLAG) == 0)    {        //在申请字节的基础上增加一个记录结构体        size += sizeof(BLOCK_T);    }    //字节对齐处理     if((size & (uint32_t)0x00000007) != 0)    {        size += 7;        size &= ~(uint32_t)0x00000007;    }    if(size <= freeByteRemaining)    {        previousBlock = &heapStart;        currentBlock = heapStart.nextFreeBlock;        while(( currentBlock->blockSize < size ) && currentBlock->nextFreeBlock != NULL)        {            previousBlock = currentBlock;            currentBlock = currentBlock->nextFreeBlock;        }        if(currentBlock != heapEnd)        {            //地址偏移            returnHeap = (void*)( (uint8_t *)(previousBlock->nextFreeBlock)+ sizeof(BLOCK_T)) ;            previousBlock->nextFreeBlock = currentBlock->nextFreeBlock;            //分配后内存块还有剩余 创建新的内存块            if((currentBlock->blockSize - size) > MIN_BLOCK_SIZE )            {                newBlock = (void*) (((uint8_t*)currentBlock) + size);                newBlock->blockSize = currentBlock->blockSize - size;                currentBlock->blockSize = size;                //将新内存块加入链表                insertBLock(newBlock);            }            //更新剩余堆大小和最小空闲块            freeByteRemaining -= currentBlock->blockSize;            if(freeByteRemaining < minFreeByteRemaining)            {                minFreeByteRemaining = freeByteRemaining;            }            //标记此块内存已被使用            currentBlock->blockSize |= BLOCK_USE_FLAG;            currentBlock->nextFreeBlock = NULL;        }    }    return returnHeap;}
  1. 对分配的字节数进行调整,首先加上一个结构体大小的字节数,每一个分配出去的内存块头部都需要加上BLOCK_T的结构体,来管理此块内存,便于内存的释放。然后做字节对齐的处理。
  2. 遍历整个链表,找到第一个拥有足够字节大小的空闲块。如果找到即可将此空闲块的地址偏移(sizeof(BLOCK_T))个字节返回给申请者,
  3. 判断此内存块分配后是否还存在剩余字节,如果有则新建一个空闲内存快,记录剩余字节数,并将它重新插入到链表中。
  4. 更新剩余堆大小和最小空闲块和标记内存被使用

接下来对分配内存时使用的insertBLock函数进行分析。此函数将新的空闲块插入至链表中。并且如果存在可合并的项(地址上连续)就进行空闲块的合并

static void insertBLock(BLOCK_T *newBlock){    BLOCK_T *iterator;    uint8_t *p;    //找到插入内存块的上一块内存  按地址排序    for(iterator = &heapStart; iterator->nextFreeBlock < newBlock; iterator = iterator->nextFreeBlock)    {    }    p = (uint8_t*)iterator;    //可以合并    if((p + iterator->blockSize) == (uint8_t*)newBlock)    {        iterator->blockSize += newBlock->blockSize;        newBlock = iterator;    }    p = (uint8_t*)newBlock;    //可以和后面合并    if((p + newBlock->blockSize) == (uint8_t*) iterator->nextFreeBlock)    {        if(iterator->nextFreeBlock != heapEnd)        {            newBlock->blockSize += iterator->nextFreeBlock->blockSize;            newBlock->nextFreeBlock = iterator->nextFreeBlock->nextFreeBlock;        }        else        {            newBlock->nextFreeBlock = heapEnd;        }    }    else    {        newBlock->nextFreeBlock = iterator->nextFreeBlock;    }    if(iterator != newBlock)    {        iterator->nextFreeBlock = newBlock;    }}
  1. 对链表进行遍历,找到待插入内存块的上一块空闲内存块(按地址排序),判断此空闲内存块与要插入的内存块是否连续,如果连续则合并。
  2. 同样将要插入的内存块与下一块内存块进行比较,如果地址连续则合并。
  3. 最后调整链表指针指向,插入完成。

    最后对内存释放函数进行分析

void myFree(void *freeBlock){    uint8_t *p = (uint8_t*)freeBlock;    BLOCK_T *blockLink;    if(p != NULL)    {        p -=  sizeof(BLOCK_T);        blockLink = (void*) p;        if((blockLink->blockSize & BLOCK_USE_FLAG)!=0)        {             if(blockLink->nextFreeBlock == NULL)             {                 //标记此内存块未使用                 blockLink->blockSize &= ~BLOCK_USE_FLAG;                 //更新剩余内存                 freeByteRemaining += blockLink->blockSize;                 insertBLock(blockLink);             }        }    }}
  1. 偏移一个sizeof(BLOCK_T)的字节长度,得到要释放的内存块原本的结构体数据(申请的字节).
  2. 如果此内存块确实被使用了(根据使用标志判断),清除使用标志,更新剩余内存大小
  3. 将这块内存重新插入至空闲内存块的链表中

    至此内存管理所需的两个函数malloc()和free()就完成了。

原创粉丝点击