内存管理
来源:互联网 发布:如何制作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;}
- 首先获取申请的数组的首地址,然后对其进行一个8字节对齐的处理。为什么是8字节呢,因为刚好结构体总共占了8个字节,这样使用方便。
- 头指针heapStart指向字节对齐的数组首地址,以后这个指针会当作遍历整个空闲链表的根指针。
- 偏移至数组尾部前8个字节,然后在此放置一个heapEnd 的结构体指针。
- 在头部放置firstFreeBlock结构体指针,并计算出总共的空闲字节大小。
- 记录剩余内存数和最小空闲块
此时整个数组的头部和尾部分别放置一个结构体,根指针指向头部。目前只存在一个空闲块。
接下来讲一下分配内存的思路,程序根据要分配的字节从头部遍历到尾部,找到第一个足够字节大小的空闲块分配给申请者,如果还存在剩余的字节则重新建立一个空闲块,并调整链表指向。
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;}
- 对分配的字节数进行调整,首先加上一个结构体大小的字节数,每一个分配出去的内存块头部都需要加上BLOCK_T的结构体,来管理此块内存,便于内存的释放。然后做字节对齐的处理。
- 遍历整个链表,找到第一个拥有足够字节大小的空闲块。如果找到即可将此空闲块的地址偏移(sizeof(BLOCK_T))个字节返回给申请者,
- 判断此内存块分配后是否还存在剩余字节,如果有则新建一个空闲内存快,记录剩余字节数,并将它重新插入到链表中。
- 更新剩余堆大小和最小空闲块和标记内存被使用
接下来对分配内存时使用的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; }}
- 对链表进行遍历,找到待插入内存块的上一块空闲内存块(按地址排序),判断此空闲内存块与要插入的内存块是否连续,如果连续则合并。
- 同样将要插入的内存块与下一块内存块进行比较,如果地址连续则合并。
最后调整链表指针指向,插入完成。
最后对内存释放函数进行分析
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); } } }}
- 偏移一个sizeof(BLOCK_T)的字节长度,得到要释放的内存块原本的结构体数据(申请的字节).
- 如果此内存块确实被使用了(根据使用标志判断),清除使用标志,更新剩余内存大小
将这块内存重新插入至空闲内存块的链表中
至此内存管理所需的两个函数malloc()和free()就完成了。
阅读全文
0 0
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- 内存管理
- PHP设计模式-单例模式
- socket
- linux学习第三篇:单用户模式与救援模式
- Ubuntu修改设备名
- SpringBoot整合Mybatis实现增删改查的功能
- 内存管理
- 学生成绩转换
- 数据结构实验一
- 总结一下数据库的 一对多、多对一、一对一、多对多 关系
- lottie-android
- java安全 ——JAAS(Java 认证和授权服务)开发指南
- python3+pygame播放声音
- python 如何工整的打印字符串
- String类常用方法