当前位置: 首页 > news >正文

【内存池】动态内存分配机制

【内存池】动态内存分配机制

  • 设计背景与目的
  • 数据结构
    • 1. 堆区结构体与函数指针
    • 2. 创建实体对象,并确定具体函数
    • 3. 内存对齐变量
    • 4. 空闲内存块设计
    • 5. 其他核心变量
  • 函数实现
    • 1. 内存初始化 my_heap_init
    • 2. 内存分配 my_heap_malloc
    • 3. 空闲块链表插入空闲块
    • 4. 内存释放 my_heap_free
  • 优势
  • 问题与解答

设计背景与目的

  设计一套基于内存池的自定义堆内存管理机制,主要用于嵌入式系统或实时系统场景,标准库的malloc/free存在明显缺陷:

  • 依赖操作系统动态内存管理,分配 / 释放时间不确定(不符合实时系统 “确定性” 要求);
  • 容易产生内存碎片(小空闲块无法合并,导致大内存请求失败);
  • 可能因误操作导致堆溢出(无边界检查),且内存使用状态不可控。

数据结构

1. 堆区结构体与函数指针

typedef struct _MY_HEAP
{void * (*malloc)(uint32_t xWantedSize);   // 分配堆区内存void (*free)(void *pv);                   // 释放堆区内存 uint32_t(*get_free_heapsize)(void);       // 获取当前可用堆大小uint32_t(*get_min_ever_free_heapsize)(void);  // 获取历史最小可用堆大小
}c_my_heap;

2. 创建实体对象,并确定具体函数

const c_my_heap my_heap = {.malloc = my_heap_malloc,.free = my_heap_free,.get_free_heapsize = my_heap_get_free_heapsize, .get_min_ever_free_heapsize = my_heap_get_min_ever_free_heapsize
}; 
// 获取单例实例的函数
const c_my_heap* get_my_heap_instance(void) {return &my_heap;
}

3. 内存对齐变量

// 对齐字节数
#define portBYTE_ALIGNMENT      8 
// 对齐字节掩码 用于内存对齐
#define portBYTE_ALIGNMENT_MASK (portBYTE_ALIGNMENT - 1)
// 对一个字节或者内存进行对齐 ******核心操作******
uint32_t address = (address + portBYTE_ALIGNMENT_MASK) & ~portBYTE_ALIGNMENT_MASK

4. 空闲内存块设计

// 定义块链接结构体
typedef struct A_BLOCK_LINK {struct A_BLOCK_LINK *pxNextFreeBlock;	// 指向下一个空闲块 size_t xBlockSize;						// 空闲块的大小 
} BlockLink_t;// 空闲块也必须做内存对齐
static const uint32_t xHeapStructSize = (sizeof(BlockLink_t) + portBYTE_ALIGNMENT_MASK) & (~portBYTE_ALIGNMENT_MASK);// 定义一个最小块,因为后期获取的空闲内存块可能可以分成两块,即可以分割的最小块大小
#define heapMINIMUM_BLOCK_SIZE     (size_t)((xHeapStructSize << 1))     // 最小块大小

5. 其他核心变量

// 内存池区域 从数组的起点地址开始维护一段 4096 字节的内存
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
// 空闲内存块头尾指针,都是哨兵节点
static BlockLink_t xStart, *pxEnd = NULL;// 获取 uint32_t 类型的最高位。
// 当该位在 BlockLink_t 的 xBlockSize 成员中被设置时,块属于应用程序。
// 当该位未设置时,该块仍然是自由堆空间的一部分。
static uint32_t xBlockAllocatedBit = 0; 		// 初始化函数中初始化为0x80000000  (1 << 31)// 记录剩余可用字节数,但不能显示内存碎片
static uint32_t xFreeBytesRemaining = 0U;
static uint32_t xMinimumEverFreeBytesRemaining = 0U;

函数实现

1. 内存初始化 my_heap_init

  • 起始内存对齐 -> 确定头尾哨兵节点(尾指针内存对齐) -> 初始化唯一空闲内存块 -> 统计内存剩余信息 -> 初始化内存分配标记位
      static void my_heap_init( void ){BlockLink_t *pxFirstFreeBlock;      // 指向第一个空闲内存块的指针uint8_t *pucAlignedHeap;            // 对齐后的堆起始地址uint32_t uxAddress;uint32_t xTotalHeapSize = configTOTAL_HEAP_SIZE;    // 调整后的实际可用堆大小// 1. 地址对齐开始处理uxAddress = (uint32_t)ucHeap;if ((uxAddress & portBYTE_ALIGNMENT_MASK) != 0){// (1) 先加上对齐边界减1的值, 确保跨越下一个对齐边界uxAddress += (portBYTE_ALIGNMENT - 1);// (2) 通过与操作清除低位,实现向下对齐uxAddress &= ~( (uint32_t) portBYTE_ALIGNMENT_MASK );// (3) 调整总堆大小xTotalHeapSize -= uxAddress - (uint32_t)ucHeap;}pucAlignedHeap = (uint8_t *)uxAddress;// 2. 初始化链表头尾节点:// xStart 用于持有空闲块链表的第一个元素的指针xStart.pxNextFreeBlock = (void *)pucAlignedHeap;xStart.xBlockSize = (uint32_t)0;// pxEnd 用于标记空闲块链表的结束,并插入在堆空间的末尾。uxAddress = ((uint32_t)pucAlignedHeap) + xTotalHeapSize;uxAddress -= sizeof(BlockLink_t);	// 留出一块做内存对齐,并防止越界访问uxAddress &= ~( (uint32_t) portBYTE_ALIGNMENT_MASK );pxEnd = (void *)uxAddress;          // 设置 pxEnd 为堆的末尾pxEnd->xBlockSize = 0;              // 结束块的大小设置为0pxEnd->pxNextFreeBlock = NULL;      // 下一个指针设为NULL// 3. 初始化唯一空闲块 开始时只有一个空闲块,该块的大小为整个堆空间pxFirstFreeBlock = (void *)pucAlignedHeap;pxFirstFreeBlock->xBlockSize = uxAddress - (uint32_t)pxFirstFreeBlock; // 计算空闲块大小pxFirstFreeBlock->pxNextFreeBlock = pxEnd; // 链接到结束块// 4. 初始化统计信息:仅有一个块,覆盖整个可用堆空间。xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;  // 初始化最小可用xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;             // 初始化可用字节// 5. 分配标志位初始化 uint32_t 变量中顶位的位置。xBlockAllocatedBit = ((uint32_t) 1) << ((sizeof(uint32_t) * heapBITS_PER_BYTE) - 1 );}
    

2. 内存分配 my_heap_malloc

  初始化检查 -> 参数检查 -> 调整大小(+xHeapStructSize)并对齐 -> 空闲块检查, 遍历空闲链表寻找合适块 -> 标记返回指向的内存空间,跳过 BlockLink_t 的结构体 -> 块分割处理 -> 创建新块(更新两个块的大小) -> 把新块插入到空闲块链表

  void *my_heap_malloc( uint32_t xWantedSize ){BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;void *pvReturn = NULL;// 1. 初始化检查if( pxEnd == NULL ) my_heap_init();// 2. 参数校验// 检查请求的块大小是否过大,最高位应设置为 0if( ( xWantedSize & xBlockAllocatedBit ) == 0 ) {if( xWantedSize > 0 ){// 3. 大小调整xWantedSize += xHeapStructSize;     // (1) 增加块头大小// (2) 进行内存对齐处理if((xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0)xWantedSize = (xWantedSize + portBYTE_ALIGNMENT_MASK) & (~portBYTE_ALIGNMENT_MASK);}// 4. 空闲内存检查: 确认有足够剩余内存if((xWantedSize > 0 ) && (xWantedSize <= xFreeBytesRemaining)){// 5. 空闲块查找:遍历空闲链表寻找合适块 // 从低地址块开始遍历链表,直到找到合适大小的块。pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while((pxBlock->xBlockSize < xWantedSize ) && (pxBlock->pxNextFreeBlock != NULL)){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}// 如果到达了结束标志,则未找到合适大小的块。if( pxBlock != pxEnd ){// 6. 标记返回指向的内存空间,跳过 BlockLink_t 的结构体pvReturn = (void *)((( uint8_t *) pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize);//  此块正在被返回使用,因此必须将其从空闲块列表中移除。pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;// 7. 块分割处理:如果找到的块过大,进行分割if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){// 该块要拆分为两个块。创建在请求的字节之后的新块。pxNewBlockLink = ( void * ) (((uint8_t *)pxBlock) + xWantedSize );// 更新后面一块与前面分配内存的一个块的大小pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;// 将新块插入空闲块列表中。 my_heap_insert_block_into_freelist( pxNewBlockLink );}// 更新剩余字节数xFreeBytesRemaining -= pxBlock->xBlockSize;if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;}// 此块正在被返回 - 它已分配并归应用程序所有,并且没有“下一个”块。 */pxBlock->xBlockSize |= xBlockAllocatedBit;pxBlock->pxNextFreeBlock = NULL; // 设置下一个块指针为 NULL}}}return pvReturn;}

3. 空闲块链表插入空闲块

  • 找合适的位置,从头开始,找到第一个pxIterator->next > Insert的地址,Interator是Insert前面一个块的地址,这里数字当然是为了便于理解,实际的是内存对齐的
  • 找到合适的空闲块插入的前一个块起始地址Interator

    // 遍历链表,找到适合插入的新块位置
    BlockLink_t *pxIterator = &xStart;
    while(pxIterator->pxNextFreeBlock < pxBlockToInsert){pxIterator = pxIterator->pxNextFreeBlock
    }
    
  • 前序合并

    // 可能与前一个块合并
    puc = (uint8_t *)pxIterator;
    if ((puc + pxIterator->xBlockSize) == (uint8_t *)pxBlockToInsert) 
    {pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; // 合并块pxBlockToInsert = pxIterator; // 更新插入块
    }
    // 这里可能先与前面的块合并后继续与后续的块合并,走下面的逻辑
    
  • 后续合并

    // 可能与后一个块合并 
    puc = (uint8_t *)pxBlockToInsert;
    if ((puc + pxBlockToInsert->xBlockSize) == (uint8_t *)pxIterator->pxNextFreeBlock) 
    {if (pxIterator->pxNextFreeBlock != pxEnd) {/* 合并两个块 */pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; // 更新链表} else {pxBlockToInsert->pxNextFreeBlock = pxEnd; // 更新结束块}
    } 
    // 插入新块 insert->next
    else {pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
    }
    
  • 完整插入空闲块代码

      static void my_heap_insert_block_into_freelist(BlockLink_t *pxBlockToInsert){BlockLink_t *pxIterator;uint8_t *puc;// 遍历链表,找到适合插入的新块位置pxIterator = &xStart;while(pxIterator->pxNextFreeBlock < pxBlockToInsert){pxIterator = pxIterator->pxNextFreeBlock;}// 可能与前一个块合并puc = (uint8_t *)pxIterator;if ((puc + pxIterator->xBlockSize) == (uint8_t *)pxBlockToInsert) {pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; // 合并块pxBlockToInsert = pxIterator; // 更新插入块}/* 可能与后一个块合并 */puc = (uint8_t *)pxBlockToInsert;if ((puc + pxBlockToInsert->xBlockSize) == (uint8_t *)pxIterator->pxNextFreeBlock) {/* 合并两个块 */if (pxIterator->pxNextFreeBlock != pxEnd) {// 更新块大小pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;// 更新链表pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; } else {pxBlockToInsert->pxNextFreeBlock = pxEnd; // 更新结束块}} // 插入新块 insert->nextelse {pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}// 如果未发生合并,插入块 前面连接 insertif (pxIterator != pxBlockToInsert) {pxIterator->pxNextFreeBlock = pxBlockToInsert;}}
    

4. 内存释放 my_heap_free

 void my_heap_free( void *pv ){uint8_t *puc = ( uint8_t * ) pv;BlockLink_t *pxLink;if( pv != NULL ){/* 被释放的内存会在它前面有一个 BlockLink_t 结构。 */puc -= xHeapStructSize;if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ){/* 该块正在返回给堆 - 它不再属于已分配块。 */pxLink->xBlockSize &= ~xBlockAllocatedBit;/* 将该块添加到空闲块列表。 */xFreeBytesRemaining += pxLink->xBlockSize;my_heap_insert_block_into_freelist( ( ( BlockLink_t * ) pxLink ) );}}}

优势

结合代码细节,该实现的核心优势如下:

  1. 内存使用可控
    • 堆大小由configTOTAL_HEAP_SIZE固定(基于静态数组ucHeap),避免堆无限制增长导致的内存溢出;
    • 通过xFreeBytesRemainingxMinimumEverFreeBytesRemaining跟踪内存使用,便于调试和资源监控。
  2. 分配效率与确定性
    • 基于链表管理空闲块,遍历和插入操作逻辑简单,避免标准库复杂的系统调用开销;
    • 分配 / 释放时间可预测(无复杂算法),符合实时系统对 “操作耗时固定” 的要求。
  3. 减少内存碎片
    • 释放内存时通过my_heap_insert_block_into_freelist实现双向合并(与前 / 后空闲块合并),减少外部碎片;
    • 分配时若空闲块过大,会拆分出多余部分重新加入空闲链表(pxNewBlockLink逻辑),提高内存利用率。
  4. 适配硬件要求
    • 强制内存对齐(portBYTE_ALIGNMENT处理),确保分配的内存地址符合硬件访问要求(如某些 CPU 需 4 字节 / 8 字节对齐);
    • 无动态内存依赖(基于静态数组),适合无操作系统或资源受限的嵌入式芯片(如 MCU)。
  5. 可维护性与安全性
    • 封装为单例模式(c_my_heap结构体),避免多实例冲突,便于全局管理;
    • 通过xBlockAllocatedBit标记块是否分配,防止重复释放或非法访问。

问题与解答

1. 为什么不使用标准库的malloc/free,而要自定义堆管理?

  • 标准库malloc/free分配时间不确定(依赖系统内存管理策略),不适合实时系统;
  • 容易产生内存碎片,且无法控制堆大小(可能溢出到其他内存区域);
  • 嵌入式系统资源有限(如小容量 RAM),自定义堆可基于静态数组固定大小,避免内存滥用。

2. 内存对齐是如何实现的?为什么需要内存对齐?

  • 通过(xWantedSize + portBYTE_ALIGNMENT_MASK) & (~portBYTE_ALIGNMENT_MASK)调整大小,通过uxAddress &= ~portBYTE_ALIGNMENT_MASK调整地址,确保满足portBYTE_ALIGNMENT对齐要求;
  • 原因:硬件限制(如 CPU 访问非对齐地址可能报错或性能下降),同时保证BlockLink_t结构体成员(如指针)的正确访问。

3. 如何处理内存碎片?代码中具体做了哪些操作?

  • 主要通过空闲块合并解决外部碎片:
    • 释放内存时,my_heap_insert_block_into_freelist先检查前序块(pxIterator)是否相邻,若相邻则合并;
    • 再检查后序块(pxIterator->pxNextFreeBlock)是否相邻,若相邻则合并;
  • 分配时若空闲块过大(剩余空间 > 最小块大小heapMINIMUM_BLOCK_SIZE),会拆分出多余部分重新加入空闲链表,避免大空闲块被浪费。

4. 代码中用链表管理空闲块,为什么选择链表而不是其他数据结构(如树)

  • 嵌入式系统对内存和计算资源敏感,链表实现简单(仅需指针和大小字段),内存开销小;
  • 代码中空闲块按地址有序排列(遍历插入时保证pxIterator->pxNextFreeBlock < pxBlockToInsert),线性遍历即可满足需求,无需更复杂的树结构(如二叉树);
  • 实时系统更关注 “确定性” 而非极致性能,链表的固定操作耗时更符合要求。

5. 堆初始化(my_heap_init)主要做了哪些工作?

  • 地址对齐:调整ucHeap的起始地址,确保满足对齐要求;
  • 初始化链表:创建哨兵节点xStartpxEnd,将整个ucHeap初始化为一个大空闲块,加入链表;
  • 初始化统计信息:设置xFreeBytesRemaining(初始为总大小)和xMinimumEverFreeBytesRemaining(跟踪最小剩余内存);
  • 初始化分配标志位:计算xBlockAllocatedBituint32_t的最高位)。

6.xBlockAllocatedBit的作用是什么?如何避免重复释放?

  • 作用:xBlockAllocatedBitxBlockSize的最高位,用于标记块是否被分配(置位表示已分配,清零表示空闲);
  • 避免重复释放:my_heap_free中先检查(pxLink->xBlockSize & xBlockAllocatedBit) != 0,仅当块处于 “已分配” 状态时才执行释放,防止重复释放导致的链表错乱。

7. 代码是否支持多线程?若不支持,如何改进?

  • 不支持。当前代码无互斥机制,多线程同时调用my_heap_mallocmy_heap_free可能导致链表指针错乱(如同时修改pxNextFreeBlock);
  • 改进:添加互斥锁(如嵌入式中的vTaskSuspendAll/xTaskResumeAll,或 POSIX 的pthread_mutex),确保内存操作的原子性。
http://www.dtcms.com/a/526413.html

相关文章:

  • 一键注册所有网站涉密网络运行维护服务外包的单位
  • 深圳市 网站建设450做自己的视频网站
  • 免费空间的个人网站为什么wordpress在ie打开很慢
  • 【1024节】一年一年又是一年
  • 武义建设局网站网站建设 技术方案
  • 网站别人备案怎么办dedecms wap网站模板下载
  • 青岛seo整站优化织梦网站如何做地区分站
  • 网站首页图片尺寸广州安全教育平台官网登录
  • 南阳网站推广站长之家alexa排名怎么看
  • 开家网站建设培训学校付钱做编程题目的网站
  • 商业网站的特点wordpress需要多少运存
  • 怎么做网站盗号市场推广的方法和规划
  • 好的h5制作网站模板下载专业类搜题软件
  • 建设部网站实名制举报佛山搜索seo网络推广
  • 基于jsp的电子商务网站开发wordpress后台添加友情链接
  • 湖州网站建设制作世界上最有趣的网站
  • Modbus面试高频问题标准答案
  • 深圳招聘一般在哪个网站岳阳网络公司
  • 网站ico图标放在哪里网站制作是怎样做的
  • 网站制作哪里好网站建设要什么
  • Wordpress验证登陆函数作品提示优化要删吗
  • 自己做的网站怎样让百度搜到php的网站怎么做的
  • 做网站怎么接私活WordPress设置两个域名
  • C++ 智能指针的使用及其原理
  • 张家港网站制作服务天元建设集团有限公司济南第六建筑工程公司
  • 信息网站方案百度做免费推广的步骤
  • 做网站和软件哪个挣钱公司装修费用如何入账
  • 建个外国网站公司网站建设开发维护工作
  • 网站设计怎么做图片透明度西安网站建设发布
  • php零基础做网站南通模板建站多少钱