STM32F4 内存管理介绍及应用
第十八章 内存管理介绍及应用
1. 内存管理简介
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,其实最终都是要实现两个函数: malloc 和 free。 malloc 函数用来内存申请, free 函数用于内存释放。
本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理
从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了n 块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。
内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。
内存分配方向如上图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
1.1 分配原理
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,完成一次分配。注意:如果当内存不够的时候(找到最后也没有找到连续 m 块空闲内存),则返回 NULL 给 p,表示分配失败。
1.2 释放原理
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。 free 函数先判断 p 指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释放,完成一次内存释放。
2. 内存管理应用示例
2.1 相关宏定义
#ifndef __MALLOC_H
#define __MALLOC_H#include "sys.h"/* 定义3个内存池 */
#define SRAMIN 0 // 内部内存池
#define SRAMCCM 1 // CCM内存池(此部分SRAM仅仅CPU可以访问!!!)
#define SRAMEX 2 // 外部内存池
#define SRAMBANK 3 // 定义支持的SRAM块数/* 定义内存管理表类型,当外扩SDRAM的时候,必须使用uint32_t类型,否则可以定义成uint16_t,以节省内存占用 */
#define MT_TYPE uint16_t/* 单块内存,内存管理所占用的全部空间大小计算公式如下:* size = MEM1_MAX_SIZE + (MEM1_MAX_SIZE / MEM1_BLOCK_SIZE) * sizeof(MT_TYPE)* 以SRAMEX为例,size = 963 * 1024 + (963 * 1024 / 32) * 2 = 1047744 ≈ 1023KB* 已知总内存容量(size),最大内存池的计算公式如下:* MEM1_MAX_SIZE = (MEM1_BLOCK_SIZE * size) / (MEM1_BLOCK_SIZE + sizeof(MT_TYPE))* 以CCM为例, MEM2_MAX_SIZE = (32 * 64) / (32 + 2) = 60.24KB ≈ 60KB*//* mem1内存参数设定.mem1完全处于内部SRAM里面 */
#define MEM1_BLOCK_SIZE 32 // 内存块大小为32字节
#define MEM1_MAX_SIZE 100 * 1024 // 最大管理内存 100K
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE // 内存表大小
/* mem2内存参数设定.mem2处于CCM,用于管理CCM(特别注意,这部分SRAM,仅CPU可以访问!!) */
#define MEM2_BLOCK_SIZE 32 // 内存块大小为32字节
#define MEM2_MAX_SIZE 60 * 1024 // 最大管理内存60K
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE // 内存表大小
/* mem3内存参数设定.mem3是外扩SRAM */
#define MEM3_BLOCK_SIZE 32 // 内存块大小为32字节
#define MEM3_MAX_SIZE 963 * 1024 // 最大管理内存963K
#define MEM3_ALLOC_TABLE_SIZE MEM3_MAX_SIZE/MEM3_BLOCK_SIZE // 内存表大小 /* 如果没有定义NULL, 定义NULL */
#ifndef NULL
#define NULL 0
#endif/* 内存管理控制器 */
struct _m_mallco_dev
{void (*init)(uint8_t); // 初始化 uint16_t (*perused)(uint8_t); // 内存使用率 uint8_t *membase[SRAMBANK]; // 内存池 管理SRAMBANK个区域的内存 MT_TYPE *memmap[SRAMBANK]; // 内存管理状态表 uint8_t memrdy[SRAMBANK]; // 内存管理是否就绪
};extern struct _m_mallco_dev mallco_dev;
void my_mem_init(uint8_t memx); // 内存管理初始化函数(外/内部调用)
uint16_t my_mem_perused(uint8_t memx) ; // 获得内存使用率(外/内部调用)
void my_mem_set(void *s, uint8_t c, uint32_t count); // 内存设置函数
void my_mem_copy(void *des, void *src, uint32_t n); // 内存拷贝函数
void myfree(uint8_t memx, void *ptr); // 内存释放(外部调用)
void *mymalloc(uint8_t memx, uint32_t size); // 内存分配(外部调用)
void *myrealloc(uint8_t memx, void *ptr, uint32_t size); // 重新分配内存(外部调用) #endif /* __MALLOC_H */
2.2 AC5/6编译器兼容
#if !(__ARMCC_VERSION >= 6010050) /* 不是AC6编译器,即使用AC5编译器时 */
/* 内存池(64字节对齐) */
static __align(64) uint8_t mem1base[MEM1_MAX_SIZE]; /* 内部SRAM内存池 */
static __align(64) uint8_t mem2base[MEM2_MAX_SIZE] __attribute__((at(0x10000000))); /* 内部CCM内存池 */
static __align(64) uint8_t mem3base[MEM3_MAX_SIZE] __attribute__((at(0x68000000))); /* 外部SRAM内存池 *//* 内存管理表 */
static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; /* 内部SRAM内存池MAP */
static MT_TYPE mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0x10000000 + MEM2_MAX_SIZE))); /* 内部CCM内存池MAP */
static MT_TYPE mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0x68000000 + MEM3_MAX_SIZE))); /* 外部SRAM内存池MAP */
#else /* 使用AC6编译器时 */
/* 内存池(64字节对齐) */
static __ALIGNED(64) uint8_t mem1base[MEM1_MAX_SIZE]; /* 内部SRAM内存池 */
static __ALIGNED(64) uint8_t mem2base[MEM2_MAX_SIZE] __attribute__((section(".bss.ARM.__at_0x10000000"))); /* 内部CCM内存池 */
static __ALIGNED(64) uint8_t mem3base[MEM3_MAX_SIZE] __attribute__((section(".bss.ARM.__at_0x68000000"))); /* 外部SRAM内存池 */
/* 内存管理表 */
static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; /* 内部SRAM内存池MAP */
static MT_TYPE mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((section(".bss.ARM.__at_0x1000F000"))); /* 内部CCM内存池MAP */
static MT_TYPE mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((section(".bss.ARM.__at_0x680F0C00"))); /* 外部SRAM内存池MAP */
#endif
/* 内存管理参数 */
const uint32_t memtblsize[SRAMBANK] = {MEM1_ALLOC_TABLE_SIZE, MEM2_ALLOC_TABLE_SIZE, MEM3_ALLOC_TABLE_SIZE}; /* 内存表大小 */
const uint32_t memblksize[SRAMBANK] = {MEM1_BLOCK_SIZE, MEM2_BLOCK_SIZE, MEM3_BLOCK_SIZE}; /* 内存分块大小 */
const uint32_t memsize[SRAMBANK] = {MEM1_MAX_SIZE, MEM2_MAX_SIZE, MEM3_MAX_SIZE};
2.3 核心操作函数
/* 内存管理控制器 */
struct _m_mallco_dev mallco_dev =
{my_mem_init, /* 内存初始化 */my_mem_perused, /* 内存使用率 */mem1base, mem2base, mem3base, /* 内存池 */mem1mapbase, mem2mapbase, mem3mapbase, /* 内存管理状态表 */0, 0, 0, /* 内存管理未就绪 */
};/*** @brief 复制内存* @param *des : 目的地址* @param *src : 源地址* @param n : 需要复制的内存长度(字节为单位)* @retval 无*/
void my_mem_copy(void *des, void *src, uint32_t n)
{uint8_t *xdes = des;uint8_t *xsrc = src;while (n--) *xdes++ = *xsrc++;
}/*** @brief 设置内存值* @param *s : 内存首地址* @param c : 要设置的值* @param count : 需要设置的内存大小(字节为单位)* @retval 无*/
void my_mem_set(void *s, uint8_t c, uint32_t count)
{uint8_t *xs = s;while (count--) *xs++ = c;
}/*** @brief 内存管理初始化* @param memx : 所属内存块* @retval 无*/
void my_mem_init(uint8_t memx)
{uint8_t mttsize = sizeof(MT_TYPE); /* 获取memmap数组的类型长度(uint16_t /uint32_t)*/my_mem_set(mallco_dev.memmap[memx], 0, memtblsize[memx] * mttsize); /* 内存状态表数据清零 */mallco_dev.memrdy[memx] = 1; /* 内存管理初始化OK */
}/*** @brief 获取内存使用率* @param memx : 所属内存块* @retval 使用率(扩大了10倍,0~1000,代表0.0%~100.0%)*/
uint16_t my_mem_perused(uint8_t memx)
{uint32_t used = 0;uint32_t i;for (i = 0; i < memtblsize[memx]; i++){if (mallco_dev.memmap[memx][i]) used++;}return (used * 1000) / (memtblsize[memx]);
}/*** @brief 内存分配(内部调用)* @param memx : 所属内存块* @param size : 要分配的内存大小(字节)* @retval 内存偏移地址* @arg 0 ~ 0xFFFFFFFE : 有效的内存偏移地址* @arg 0xFFFFFFFF : 无效的内存偏移地址*/
static uint32_t my_mem_malloc(uint8_t memx, uint32_t size)
{signed long offset = 0;uint32_t nmemb; /* 需要的内存块数 */uint32_t cmemb = 0; /* 连续空内存块数 */uint32_t i;if (!mallco_dev.memrdy[memx]){mallco_dev.init(memx); /* 未初始化,先执行初始化 */}if (size == 0) return 0xFFFFFFFF; /* 不需要分配 */nmemb = size / memblksize[memx]; /* 获取需要分配的连续内存块数 */if (size % memblksize[memx]) nmemb++;for (offset = memtblsize[memx] - 1; offset >= 0; offset--) /* 搜索整个内存控制区 */{if (!mallco_dev.memmap[memx][offset]){cmemb++; /* 连续空内存块数增加 */}else {cmemb = 0; /* 连续内存块清零 */}if (cmemb == nmemb) /* 找到了连续nmemb个空内存块 */{for (i = 0; i < nmemb; i++) /* 标注内存块非空 */{mallco_dev.memmap[memx][offset + i] = nmemb;}return (offset * memblksize[memx]); /* 返回偏移地址 */}}return 0xFFFFFFFF; /* 未找到符合分配条件的内存块 */
}/*** @brief 释放内存(内部调用)* @param memx : 所属内存块* @param offset : 内存地址偏移* @retval 释放结果* @arg 0, 释放成功;* @arg 1, 释放失败;* @arg 2, 超区域了(失败);*/
static uint8_t my_mem_free(uint8_t memx, uint32_t offset)
{int i;if (!mallco_dev.memrdy[memx]) /* 未初始化,先执行初始化 */{mallco_dev.init(memx);return 1; /* 未初始化 */}if (offset < memsize[memx]) /* 偏移在内存池内. */{int index = offset / memblksize[memx]; /* 偏移所在内存块号码 */int nmemb = mallco_dev.memmap[memx][index]; /* 内存块数量 */for (i = 0; i < nmemb; i++) /* 内存块清零 */{mallco_dev.memmap[memx][index + i] = 0;}return 0;}else{return 2; /* 偏移超区了 */}
}/*** @brief 释放内存(外部调用)* @param memx : 所属内存块* @param ptr : 内存首地址* @retval 无*/
void myfree(uint8_t memx, void *ptr)
{uint32_t offset;if (ptr == NULL) return; /* 地址为0. */offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];my_mem_free(memx, offset); /* 释放内存 */
}/*** @brief 分配内存(外部调用)* @param memx : 所属内存块* @param size : 要分配的内存大小(字节)* @retval 分配到的内存首地址.*/
void *mymalloc(uint8_t memx, uint32_t size)
{uint32_t offset;offset = my_mem_malloc(memx, size);if (offset == 0xFFFFFFFF) /* 申请出错 */{return NULL; /* 返回空(0) */}else /* 申请没问题, 返回首地址 */{return (void *)((uint32_t)mallco_dev.membase[memx] + offset);}
}/*** @brief 重新分配内存(外部调用)* @param memx : 所属内存块* @param *ptr : 旧内存首地址* @param size : 要分配的内存大小(字节)* @retval 新分配到的内存首地址.*/
void *myrealloc(uint8_t memx, void *ptr, uint32_t size)
{uint32_t offset;offset = my_mem_malloc(memx, size);if (offset == 0xFFFFFFFF) /* 申请出错 */{return NULL; /* 返回空(0) */}else /* 申请没问题, 返回首地址 */{my_mem_copy((void *)((uint32_t)mallco_dev.membase[memx] + offset), ptr, size); /* 拷贝旧内存内容到新内存 */myfree(memx, ptr); /* 释放旧内存 */return (void *)((uint32_t)mallco_dev.membase[memx] + offset); /* 返回新内存首地址 */}
}
2.4 主函数测试
#include "bsp_init.h"
#include "stdio.h"
#include "SerialInvoker.h"
#include "sram.h"
#include "malloc.h"const char *SRAM_NAME_BUF[SRAMBANK] = {" SRAMIN ", " SRAMCCM ", " SRAMEX "};int main(void)
{uint8_t paddr[20]; // 存放P Addr:+p地址的ASCII值uint16_t memused = 0; // 记录已用SRAM大小uint8_t key_value = 0;uint8_t i = 0;uint8_t *p = 0;uint8_t *tp = 0;uint8_t sramx = 0; // 默认为内部SRAMbsp_init();sram_init();serial_invoker_init(84);my_mem_init(SRAMIN); // 初始化内部SRAMmy_mem_init(SRAMCCM); // 初始化CCM SRAMmy_mem_init(SRAMEX); // 初始化外部SRAMLCD_ShowString(30,110,200,16,16,"KEY0:Malloc & WR & Show");LCD_ShowString(30,130,200,16,16,"KEY_UP:SRAMx KEY1:Free");LCD_ShowString(60,160,200,16,16," SRAMIN ");LCD_ShowString(30,176,200,16,16,"SRAMIN USED:");LCD_ShowString(30,192,200,16,16,"SRAMCCM USED:");LCD_ShowString(30,208,200,16,16,"SRAMEX USED:");while(1){key_value = key_scan(0);switch(key_value){case KEY0_Press:p = mymalloc(sramx, 2048); // 按下KEY0申请2KB空间if(p!= NULL){sprintf((char *)p, "Memory Malloc Test%03d", i); // 写入数据LCD_ShowString(30,260,209,16,16,(char*)p);}break;case KEY1_Press:myfree(sramx, p); // 按下KEY1释放申请的空间p = 0;break;case WKUP_Press:sramx++;if(sramx > SRAMBANK) sramx = 0;LCD_ShowString(60,160,200,16,16,(char*)SRAM_NAME_BUF[sramx]);break;}if(tp != p){tp = p;sprintf((char *)paddr, "P Addr:0X%08X", (uint32_t)tp);LCD_ShowString(30,240,209,16,16,(char*)paddr);if(p){LCD_ShowString(30,260,280,16,16,(char*)p);}else{LCD_Fill(30,260,280,200,WHITE);}}delay_ms(10);i++;if((i % 20) == 0){memused = my_mem_perused(SRAMIN); // 获取内存使用率sprintf((char*)paddr, "%d.%01d%%", memused / 10, memused % 10);LCD_ShowString(30+112,176,200,16,16,(char*)paddr);memused = my_mem_perused(SRAMCCM); // 获取内存使用率sprintf((char*)paddr, "%d.%01d%%", memused / 10, memused % 10);LCD_ShowString(30+112,192,200,16,16,(char*)paddr);memused = my_mem_perused(SRAMEX); // 获取内存使用率sprintf((char*)paddr, "%d.%01d%%", memused / 10, memused % 10);LCD_ShowString(30+112,208,200,16,16,(char*)paddr);LED_TOGGLE(LED0_GPIO_Pin);}}
}
3. malloc API总结
3.1 关键宏定义
- 内存区域索引:
SRAMIN
: 0 (内部 SRAM)SRAMCCM
: 1 (CCM SRAM)SRAMEX
: 2 (外部 SRAM)SRAMBANK
: 3 (支持的内存区域总数)
- 内存块配置:
MEM1_BLOCK_SIZE
: 内部 SRAM 内存块大小 (32 字节)MEM2_BLOCK_SIZE
: CCM SRAM 内存块大小 (32 字节)MEM3_BLOCK_SIZE
: 外部 SRAM 内存块大小 (32 字节)
- 内存池大小配置:
MEM1_MAX_SIZE
: 内部 SRAM 最大管理内存 (100KB)MEM2_MAX_SIZE
: CCM SRAM 最大管理内存 (60KB)MEM3_MAX_SIZE
: 外部 SRAM 最大管理内存 (963KB)
- 内存管理表大小配置:
MEM1_ALLOC_TABLE_SIZE
: 内部 SRAM 内存管理表条目数MEM2_ALLOC_TABLE_SIZE
: CCM SRAM 内存管理表条目数MEM3_ALLOC_TABLE_SIZE
: 外部 SRAM 内存管理表条目数
- 管理表类型:
MT_TYPE
: 内存管理表条目类型 (uint16_t
)
3.2 结构体
struct _m_mallco_dev mallco_dev
(全局变量):
这是一个全局的内存管理控制器结构体,用户通常不需要直接访问其内部成员,而是通过其提供的函数指针和全局的mymalloc
、myfree
等函数进行操作。
3.3 函数
-
void my_mem_init(uint8_t memx)
- 功能: 初始化指定内存区域的内存管理系统。在使用任何内存分配函数之前,必须先对相应的内存区域进行初始化。
- 参数:
memx
: 内存区域索引。可以是SRAMIN
,SRAMCCM
, 或SRAMEX
。
- 返回值: 无。
- 备注:
mymalloc
函数在检测到未初始化时会自动调用此函数,但显式调用可以确保在第一次分配前就绪。
-
uint16_t my_mem_perused(uint8_t memx)
- 功能: 获取指定内存区域的内存使用率。
- 参数:
memx
: 内存区域索引。
- 返回值: 内存使用率,范围为 0 到 1000(代表 0.0% 到 100.0%)。
-
void *mymalloc(uint8_t memx, uint32_t size)
- 功能: 在指定内存区域分配指定大小的内存。
- 参数:
memx
: 内存区域索引。size
: 要分配的内存大小,单位为字节。
- 返回值:
- 成功:分配到的内存块的首地址指针。
- 失败(如内存不足):
NULL
。
-
void myfree(uint8_t memx, void *ptr)
- 功能: 释放指定内存区域中之前分配的内存。
- 参数:
memx
: 内存区域索引。ptr
: 要释放的内存块的首地址指针。
- 返回值: 无。
- 备注: 释放
NULL
指针是安全的,函数会忽略。
-
void *myrealloc(uint8_t memx, void *ptr, uint32_t size)
- 功能: 重新分配指定内存区域中已分配的内存块,改变其大小。
- 参数:
memx
: 内存区域索引。ptr
: 现有内存块的首地址指针(如果为NULL
,则行为类似于mymalloc
)。size
: 新的内存块大小,单位为字节。
- 返回值:
- 成功:新分配到的内存块的首地址指针(可能与旧地址不同)。
- 失败(如内存不足):
NULL
(此时原内存块保持不变)。
- 备注: 如果新大小小于旧大小,数据会被截断;如果新大小大于旧大小,多余的部分内容未定义。
3.4 硬件/环境要求
- STM32F4 微控制器: 程序是为 STM32F4 系列设计的,利用了其内部 SRAM、CCM SRAM 和 FSMC 外设来访问外部 SRAM。
- 外部 SRAM: 如果使用
SRAMEX
(外部内存池),需要确保硬件上已连接外部 SRAM,并且 STM32 的 FSMC/FMC 已正确配置以访问该 SRAM。 - 编译器:
- AC5 (ARM Compiler 5): 能够直接解析
__attribute__((at(address)))
表达式。 - AC6 (ARM Compiler 6): 必须通过**链接器脚本 (
.sct
文件)**显式定义外部 SRAM 区域,并将mem3base
、mem3mapbase
和其他需要放置在外部 SRAM 的变量(例如test_data
)分配到这些区域。C 代码中的__attribute__((section(".bss.ARM.__at_address")))
仅作提示,最终由链接器脚本决定。
- AC5 (ARM Compiler 5): 能够直接解析
3.4 基本使用流程
- 初始化内存管理:
在你的main
函数或其他初始化代码中,根据需要初始化对应的内存池。如果程序会用到所有三个内存池,则对每个池都调用一次my_mem_init
。
// main.c
bsp_init(); // 硬件初始化
sram_init(); // 外部SRAM FSMC初始化// 初始化外部SRAM内存池
my_mem_init(SRAMEX);
// 如果也使用内部SRAM内存池和CCM内存池,也需要初始化
// my_mem_init(SRAMIN);
// my_mem_init(SRAMCCM);
- 分配内存:
使用mymalloc
函数从指定内存池分配内存。
uint8_t *buffer1;
uint32_t *buffer2;// 从外部SRAM分配1KB (1024字节)
buffer1 = (uint8_t *)mymalloc(SRAMEX, 1024);
if (buffer1 != NULL) {// 内存分配成功,可以使用 buffer1// ...
} else {// 内存分配失败// ...
}// 从CCM SRAM分配50个uint32_t (200字节)
buffer2 = (uint32_t *)mymalloc(SRAMCCM, 50 * sizeof(uint32_t));
if (buffer2 != NULL) {// ...
}
- 使用内存:
if (buffer1 != NULL) {for (int i = 0; i < 1024; i++) {buffer1[i] = i % 256;}
}
- 释放内存:
if (buffer1 != NULL) {myfree(SRAMEX, buffer1);buffer1 = NULL; // 养成良好习惯,释放后置为NULL
}
if (buffer2 != NULL) {myfree(SRAMCCM, buffer2);buffer2 = NULL;
}
- 重新分配内存 (可选):
uint8_t *realloc_buffer = (uint8_t *)mymalloc(SRAMEX, 100);
// ... 使用 realloc_buffer ...// 将 realloc_buffer 的大小调整为 200 字节
realloc_buffer = (uint8_t *)myrealloc(SRAMEX, realloc_buffer, 200);
if (realloc_buffer != NULL) {// 重新分配成功// ...
} else {// 重新分配失败,原内存块仍有效
}
- 获内存使用率 (可选):
uint16_t percent_used = my_mem_perused(SRAMEX);
// 例如,如果 percent_used 是 500,表示 50.0% 使用率
4. 内存管理相关函数(HAL库及自定义实现)
4.1 HAL 库内置内存管理
4.1.1 堆栈管理 (启动文件配置)
/* 在启动文件 (startup_stm32f4xx.s) 中定义 */
Stack_Size EQU 0x800 ; 2KB 栈空间
Heap_Size EQU 0x800 ; 2KB 堆空间
4.1.2 内存分配函数
// 标准C库函数 (HAL默认使用)
void *malloc(size_t size); // 动态内存分配
void free(void *ptr); // 释放内存
void *calloc(size_t num, size_t size); // 分配并清零
void *realloc(void *ptr, size_t size); // 重新分配
4.2 自定义内存管理方案
4.2.1 分块内存管理 (Memory Pool)
// 内存池结构体
typedef struct {uint32_t *memStart; // 内存池起始地址uint32_t memSize; // 内存池总大小uint32_t blockSize; // 每个块大小uint32_t blockCount; // 块总数uint8_t *memMap; // 内存块状态表
} MemPool_t;// 初始化内存池
void MemPool_Init(MemPool_t *pool, void *memAddr, uint32_t size, uint32_t blockSize) {pool->memStart = (uint32_t*)memAddr;pool->memSize = size;pool->blockSize = blockSize;pool->blockCount = size / blockSize;// 分配状态表 (1字节管理1块)pool->memMap = (uint8_t*)malloc(pool->blockCount);memset(pool->memMap, 0, pool->blockCount); // 全部标记为空闲
}// 分配内存块
void* MemPool_Alloc(MemPool_t *pool) {for(uint32_t i = 0; i < pool->blockCount; i++) {if(pool->memMap[i] == 0) { // 找到空闲块pool->memMap[i] = 1; // 标记已用return (void*)((uint32_t)pool->memStart + i * pool->blockSize);}}return NULL; // 无可用空间
}// 释放内存块
void MemPool_Free(MemPool_t *pool, void *ptr) {uint32_t offset = (uint32_t)ptr - (uint32_t)pool->memStart;uint32_t index = offset / pool->blockSize;if(index < pool->blockCount) {pool->memMap[index] = 0; // 标记为空闲}
}
4.2.2 链表式内存管理
// 内存块控制结构
typedef struct MemBlock {struct MemBlock *next; // 下一个空闲块size_t size; // 块大小 (包含控制头)uint8_t used; // 使用标志
} MemBlock_t;// 内存池初始化
#define MEM_POOL_SIZE 4096
static uint8_t memPool[MEM_POOL_SIZE];void Mem_Init(void) {MemBlock_t *block = (MemBlock_t*)memPool;block->size = MEM_POOL_SIZE;block->used = 0;block->next = NULL;
}// 内存分配
void* Mem_Alloc(size_t size) {// 对齐要求 (4字节对齐)size = (size + 3) & ~3;MemBlock_t *best = NULL;MemBlock_t **prev = &((MemBlock_t*)memPool)->next;for(MemBlock_t *curr = (MemBlock_t*)memPool; curr; prev = &curr->next, curr = curr->next) {if(!curr->used && curr->size >= size + sizeof(MemBlock_t)) {if(!best || curr->size < best->size) {best = curr;}}}if(!best) return NULL; // 内存不足// 分割块 (如果剩余空间足够)if(best->size - size > sizeof(MemBlock_t) + 4) {MemBlock_t *newBlock = (MemBlock_t*)((uint8_t*)best + sizeof(MemBlock_t) + size);newBlock->size = best->size - size - sizeof(MemBlock_t);newBlock->used = 0;newBlock->next = best->next;best->size = size + sizeof(MemBlock_t);best->next = newBlock;}best->used = 1;return (void*)((uint8_t*)best + sizeof(MemBlock_t));
}// 内存释放
void Mem_Free(void *ptr) {if(!ptr) return;MemBlock_t *block = (MemBlock_t*)((uint8_t*)ptr - sizeof(MemBlock_t));block->used = 0;// 合并相邻空闲块MemBlock_t *curr = (MemBlock_t*)memPool;while(curr) {if(!curr->used && curr->next && !curr->next->used) {curr->size += curr->next->size;curr->next = curr->next->next;} else {curr = curr->next;}}
}
4.3 外部SRAM内存管理
// 使用FSMC外扩SRAM的内存管理
#define EXT_SRAM_BASE 0x60000000
#define EXT_SRAM_SIZE (512 * 1024) // 512KB// 初始化外部SRAM内存池
MemPool_t extSRAM_Pool;void ExtSRAM_Mem_Init(void) {// 先初始化FSMC (参考前文FSMC章节)MX_FSMC_Init();// 初始化内存池 (块大小=256字节)MemPool_Init(&extSRAM_Pool, (void*)EXT_SRAM_BASE, EXT_SRAM_SIZE, 256);
}// 从外部SRAM分配
void* ExtSRAM_Alloc(size_t size) {uint32_t blocks = (size + 255) / 256; // 计算所需块数uint8_t *base = NULL;// 寻找连续空闲块for(uint32_t i = 0; i < extSRAM_Pool.blockCount - blocks; i++) {uint32_t j;for(j = 0; j < blocks; j++) {if(extSRAM_Pool.memMap[i + j] != 0) break;}if(j == blocks) { // 找到连续空闲块base = (uint8_t*)extSRAM_Pool.memStart + i * 256;for(j = 0; j < blocks; j++) {extSRAM_Pool.memMap[i + j] = 1; // 标记为已用}return base;}}return NULL; // 分配失败
}// 释放外部SRAM内存
void ExtSRAM_Free(void *ptr, size_t size) {uint32_t offset = (uint32_t)ptr - (uint32_t)extSRAM_Pool.memStart;uint32_t startBlock = offset / 256;uint32_t blocks = (size + 255) / 256;for(uint32_t i = 0; i < blocks; i++) {if(startBlock + i < extSRAM_Pool.blockCount) {extSRAM_Pool.memMap[startBlock + i] = 0; // 标记为空闲}}
}
4.4 内存统计与监控
// 内存使用统计结构
typedef struct {uint32_t totalSize; // 总内存大小uint32_t usedSize; // 已使用内存uint32_t maxUsed; // 历史最大使用量uint32_t allocCount; // 当前分配块数uint32_t freeCount; // 当前空闲块数uint32_t allocFailures; // 分配失败次数
} MemStats_t;// 全局内存统计
MemStats_t memStats;// 更新内存统计 (在分配/释放时调用)
void Update_Mem_Stats(size_t size, int alloc) {if(alloc) {memStats.usedSize += size;memStats.allocCount++;if(memStats.usedSize > memStats.maxUsed) {memStats.maxUsed = memStats.usedSize;}} else {memStats.usedSize -= size;memStats.freeCount++;}
}// 内存溢出保护宏
#define SAFE_ALLOC(ptr, size) do { \ptr = malloc(size); \if(!ptr) { \memStats.allocFailures++; \ERROR_HANDLER(); \} else { \Update_Mem_Stats(size, 1); \} \
} while(0)#define SAFE_FREE(ptr, size) do { \if(ptr) { \free(ptr); \Update_Mem_Stats(size, 0); \ptr = NULL; \} \
} while(0)
4.5 内存保护机制
4.5.1 内存边界保护
// 带保护头的内存块
typedef struct {uint32_t guardStart; // 起始保护字size_t size; // 实际分配大小uint8_t data[]; // 用户数据
} GuardedBlock_t;// 分配带保护的内存
void* Guarded_Malloc(size_t size) {GuardedBlock_t *block = malloc(sizeof(GuardedBlock_t) + size + sizeof(uint32_t));if(!block) return NULL;block->guardStart = 0xDEADBEEF;block->size = size;uint32_t *guardEnd = (uint32_t*)((uint8_t*)block + sizeof(GuardedBlock_t) + size);*guardEnd = 0xCAFEBABE;return block->data;
}// 检查内存损坏
int Check_Mem_Corruption(void *ptr) {GuardedBlock_t *block = (GuardedBlock_t*)((uint8_t*)ptr - offsetof(GuardedBlock_t, data));uint32_t *guardEnd = (uint32_t*)((uint8_t*)ptr + block->size);if(block->guardStart != 0xDEADBEEF || *guardEnd != 0xCAFEBABE) {return 1; // 内存损坏}return 0; // 内存正常
}// 释放带保护的内存
void Guarded_Free(void *ptr) {if(Check_Mem_Corruption(ptr)) {ERROR_HANDLER(); // 内存损坏处理}GuardedBlock_t *block = (GuardedBlock_t*)((uint8_t*)ptr - offsetof(GuardedBlock_t, data));free(block);
}
4.5.2 内存泄漏检测
// 内存分配记录
typedef struct {void *ptr;size_t size;const char *file;int line;
} AllocRecord;#define MAX_RECORDS 100
static AllocRecord allocRecords[MAX_RECORDS];
static int recordCount = 0;// 带调试信息的分配
void* Debug_Malloc(size_t size, const char *file, int line) {void *ptr = malloc(size);if(ptr && recordCount < MAX_RECORDS) {allocRecords[recordCount].ptr = ptr;allocRecords[recordCount].size = size;allocRecords[recordCount].file = file;allocRecords[recordCount].line = line;recordCount++;}return ptr;
}// 带调试信息的释放
void Debug_Free(void *ptr, const char *file, int line) {for(int i = 0; i < recordCount; i++) {if(allocRecords[i].ptr == ptr) {// 移除记录for(int j = i; j < recordCount - 1; j++) {allocRecords[j] = allocRecords[j+1];}recordCount--;free(ptr);return;}}// 未找到记录 (双重释放或无效指针)ERROR_HANDLER();
}// 检测内存泄漏
void Check_Mem_Leaks(void) {if(recordCount > 0) {printf("Memory leaks detected: %d\n", recordCount);for(int i = 0; i < recordCount; i++) {printf("Leak %d: %d bytes at %p (%s:%d)\n", i+1, allocRecords[i].size, allocRecords[i].ptr, allocRecords[i].file, allocRecords[i].line);}}
}// 使用宏简化调用
#define MALLOC(size) Debug_Malloc(size, __FILE__, __LINE__)
#define FREE(ptr) Debug_Free(ptr, __FILE__, __LINE__)
4.6 多内存池管理
// 内存池类型
typedef enum {MEM_POOL_FAST, // 内部SRAM (快速)MEM_POOL_LARGE, // 外部SRAM (大容量)MEM_POOL_PERMANENT // 永久存储 (Flash模拟)
} MemPoolType;// 多池内存管理
void* Pooled_Malloc(size_t size, MemPoolType type) {switch(type) {case MEM_POOL_FAST:return InternalSRAM_Alloc(size);case MEM_POOL_LARGE:return ExtSRAM_Alloc(size);case MEM_POOL_PERMANENT:return FlashStorage_Alloc(size);default:return NULL;}
}void Pooled_Free(void *ptr, MemPoolType type) {switch(type) {case MEM_POOL_FAST:InternalSRAM_Free(ptr);break;case MEM_POOL_LARGE:ExtSRAM_Free(ptr);break;case MEM_POOL_PERMANENT:// 永久存储通常不支持释放break;}
}
文中完整工程下载:https://github.com/hazy1k/STM32F4-Quick-Start-Guide-HAL/tree/main/2.code