第24章 内存管理及应用
第二十四章 内存管理及应用
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; // 默认为内部SRAM
bsp_init();
sram_init();
serial_invoker_init(84);
my_mem_init(SRAMIN); // 初始化内部SRAM
my_mem_init(SRAMCCM); // 初始化CCM SRAM
my_mem_init(SRAMEX); // 初始化外部SRAM
LCD_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;
}
}

浙公网安备 33010602011771号