内存管理

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

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

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

#define BLOCK_USE_FLAG 0x80000000
typedef 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()就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值