第24章 内存管理及应用

第二十四章 内存管理及应用

1. 内存管理简介

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,其实最终都是要实现两个函数: malloc 和 free。 malloc 函数用来内存申请, free 函数用于内存释放。

本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理

屏幕截图 20250814 103442png

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了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 (全局变量):
    这是一个全局的内存管理控制器结构体,用户通常不需要直接访问其内部成员,而是通过其提供的函数指针和全局的 mymallocmyfree 等函数进行操作。

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 区域,并将 mem3basemem3mapbase 和其他需要放置在外部 SRAM 的变量(例如 test_data)分配到这些区域。C 代码中的 __attribute__((section(".bss.ARM.__at_address"))) 仅作提示,最终由链接器脚本决定。

3.4 基本使用流程

  1. 初始化内存管理:
    在你的 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);
  1. 分配内存:
    使用 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) {
    // ...
}
  1. 使用内存:
if (buffer1 != NULL) {
    for (int i = 0; i < 1024; i++) {
        buffer1[i] = i % 256;
    }
}
  1. 释放内存:
if (buffer1 != NULL) {
    myfree(SRAMEX, buffer1);
    buffer1 = NULL; // 养成良好习惯,释放后置为NULL
}
if (buffer2 != NULL) {
    myfree(SRAMCCM, buffer2);
    buffer2 = NULL;
}
  1. 重新分配内存 (可选):
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 {
    // 重新分配失败,原内存块仍有效
}
  1. 获内存使用率 (可选):
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;
    }
}

posted @ 2025-08-14 11:40  hazy1k  阅读(6)  评论(0)    收藏  举报