【STM32】内存管理

首先问个问题,你知道如何在LCD上显示SD卡文件浏览?-----需要读取所有文件名到内存,然后才能显示到LCD上。

一般的方法:是定义一个数组来存储文件名

1:需要知道最大文件名的长度

2:需要知道文件的个数。100?1000?10000?

倘若没有内存管理的话,我们得定义一个uint8_t fileNametBL【10000】【255】的数组!你想想会占用多少的内存呢?-----2550k字节内存(如果实际文件大小没有2550k字节,则会大大造成浪费!)

一、内存管理简介

内存管理**,是指软件运行时对MCU内存资源的** 分配 使用的技术。其最主要的目的是: 如何高效、快速的分配**,并且在**适当的时候释放和回收内存资源**

内存使用三部曲:内存申请(分配)

内存使用

内存释放

内存管理的实现方式有很多种,最终都是实现2个函数:malloc [用于内存的申请]和free[用于内存的释放]

标准的C语言库也提供了mallow函数和free函数来实现动态申请和释放内存。那为啥我们不用C语言库的呢?

原因如下:

  • 占用大量的代码空间 不适合在资源紧缺的嵌入式系统当中

  • 没有线程安全的相关机制

  • 运行有不确定性,每次调用这些函数时花费的时间可能都不一样

  • 内存碎片化

所以我们接下来会学习一种内存管理叫----"分块式内存管理"

二、分块式内存管理

(一) 介绍

分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为n块,对应的内存管理表,大小也为n,内存管理表的每一个项对应内存池的一个内存。

内存管理表的项值代表的意义:

当该项值为0时,代表对应的内存块未被占用。

当该项值为非零时,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。

当内存管理初始化时,内存管理表全部清零,表示没有任何内存块被占用

1.分配方向

从顶------>底的分配方向:即先从最末端开始找空内存。

2.分配原理:

当指针p调用malloc申请内存时:

1.先判断p要分配的内存块数(m)

2.从第n项开始,向下查找,直到找到有m块连续的空内存块(即对应内存管理表项为0)

3.将这m个内存管理表项的值都设置为m(标记为占用)

4.把最后的这个空内存块的地址返回指针p,完成一次分配

注:若当内存不够时(就是找到最后也没有找到连续空闲m块空闲内存),则返回MULL给p,表示分配失败!

3.释放原理

当指针p申请的内存用完,需要释放的时候,调用free函数实现。

free函数实现:

1.判断p指向的内存地址所对应的内存块

2.找到对应的内存管理项目,得到p所占用的内存块数目m

3.将这m个内存管理项目的值都清零,标记释放,完成一次内存释放

4.管理内存情况(基于STM32F429):

内存池大小:160*1024 B

内存块大小:64B

内存块数目 = 管理表项数目 =内存池大小/内存块大小= 160 * 1024 / 64

管理表项大小:2B(当申请内存后,内存管理表会做标记,最多是5120块,用2B就够了,,因为5120<65535)

占用内存大小=内存池+内存管理表

内存池占用的内存大小=内存块大小 * 内存块数

(二)内存管理使用

1.内存管理控制器结构体

这里有重要的内存管理控制器结构体和内存表参数宏定义

复制代码
/* mem1 内存池1参数设定.mem1 完全处于F429内部 SRAM (最大192k)里面. */
#define MEM1_BLOCK_SIZE 64 /* 内存块大小为 64 字节 */
#define MEM1_MAX_SIZE 160 * 1024 /* 最大管理内存 160K */
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE /* 内存池1的内存表大小 */
 
/* mem2 内存池2参数设定.mem2 处于 CCM,用于管理 CCM(特别注意,这部分 SRAM,仅 CPU 可以访问!!) */
#define MEM2_BLOCK_SIZE 64 /* 内存块大小为 64 字节 */
#define MEM2_MAX_SIZE 60 * 1024 /* 最大管理内存 60K */
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE /* 内存池2的内存表大小 */
 
/* mem3 内存池3参数设定.mem3的内存池处于外部 SDRAM 里面 */
#define MEM3_BLOCK_SIZE 64 /* 内存块大小为 64 字节 */
#define MEM3_MAX_SIZE 28912 * 1024 /* 最大管理内存 28912K */
#define MEM3_ALLOC_TABLE_SIZE MEM3_MAX_SIZE/MEM3_BLOCK_SIZE /* 内存池3的内存表大小 */
​
​
/* 内存池(32 字节对齐) */
static __align(32) uint8_t mem1base[MEM1_MAX_SIZE]; /* 内部 SRAM 内存池 */ 
static __align(32) uint8_t mem2base[MEM2_MAX_SIZE]
__attribute__((at(0X10000000))); /* 内部 CCM 内存池 */
static __align(32) uint8_t mem3base[MEM3_MAX_SIZE]
__attribute__((at(0XC01F4000)));
/* 外部 SDRAM 内存池,前面 2M 给 LTDC 用了(1280*800*2) */
/* 内存管理表 */
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(0XC01F4000+ MEM3_MAX_SIZE))); /* 外部 SRAM 内存池 MAP */
#else /* 使用 AC6 编译器时 */
/* 内存池(32 字节对齐) */
static __ALIGNED(32) uint8_t mem1base[MEM1_MAX_SIZE]; 
/* 内部 SRAM 内存池 */
static __ALIGNED(32) uint8_t mem2base[MEM2_MAX_SIZE]
__attribute__((section(".bss.ARM.__at_0X10000000"))); 
/* 内部 CCM 内存池 */
static __ALIGNED(32) uint8_t mem3base[MEM3_MAX_SIZE]
__attribute__((section(".bss.ARM.__at_0XC01F4000")));
/* 外部 SDRAM 内存池,前面 2M 给 LTDC 用了(1280*800*2) */
/* 内存管理表 */
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_0XC1E30000"))); /* 外部 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}; 
/* 内存管理控制器 */
struct _m_mallco_dev mallco_dev=
{
  my_mem_init, /* 内存初始化 */
  my_mem_perused, /* 内存使用率 */
  mem1base,mem2base,mem3base, /* 内存池 */
  mem1mapbase,mem2mapbase,mem3mapbase, /* 内存管理状态表 */
  0, 0, 0, /* 内存管理未就绪 */
};
2.初始化内存
复制代码
void my_mem_init(uint8_t memx)  
{  
    my_mem_set(mallco_dev.memmap[memx], 0, memtblsize[memx] * 4);  /* 内存状态表数据清零 */
    mallco_dev.memrdy[memx] = 1;                                   /* 内存管理初始化OK */
}

其中

复制代码
void my_mem_set(void *s, uint8_t c, uint32_t count)  
{  
    uint8_t *xs = s;  
    while (count--)
        *xs++ = c;  
}  

获取内存使用率

复制代码
/**
 * @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]);  
}
3.申请内存
复制代码
/**
 * @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       size : 要分配的内存大小(字节)
 * @retval      内存偏移地址
 *   @arg       0 ~ 0xFFFFFFFE : 有效的内存偏移地址
 *   @arg       0xFFFFFFFF     : 无效的内存偏移地址
 */
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;                                          /* 未找到符合分配条件的内存块 */
}
4.操作内存
复制代码
/**
 * @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;  
}  
​
5.释放内存
复制代码
/**
 * @brief       释放内存(内部调用)
 * @param       memx   : 所属内存块
 * @param       offset : 内存地址偏移
 * @retval      释放结果
 *   @arg       0, 释放成功;
 *   @arg       1, 释放失败;
 *   @arg       2, 超区域了(失败);
 */
uint8_t my_mem_free(uint8_t memx, uint32_t offset)
{
    int i;
​
    if (!mallco_dev.memrdy[memx])                   /* 未初始化,先执行初始化 */
    {
        mallco_dev.init(memx);
        return 1;                                   /* 未初始化 */
    }
    //通过offset确定偏移在第几个内存块以及获取内存块数
    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;                                  /* 偏移超区了. */
    }
}
相关推荐
森旺电子28 分钟前
STM32 启动到运行的完整流程
stm32·单片机·嵌入式硬件
IT_阿水35 分钟前
基于STM32的智慧物联网系统板---thingscloud平台连接
stm32·嵌入式硬件·物联网
森旺电子40 分钟前
STM32指令集详细介绍
stm32·单片机·嵌入式硬件
星空真迷人1 小时前
嵌入式 WiFi 加持 普通设备也能联网 “唠嗑”
arm开发·stm32·单片机·嵌入式硬件·物联网·iot
码咔吧咔2 小时前
STM32 系列 MCU 常见接口类型
stm32·嵌入式硬件
tianyue10014 小时前
STM32G431 ADC 多个channel 采集
stm32·单片机·嵌入式硬件
安生生申18 小时前
STM32 ESP8266连接ONENET
c语言·stm32·单片机·嵌入式硬件·esp8266
youcans_1 天前
【动手学STM32G4】(3)STM32G431之定时器
stm32·单片机·嵌入式硬件·定时器
硬汉嵌入式1 天前
ST最新推出的CMSIS-Driver,一套驱动完成对所有STM32系列的驱动支持
stm32·st·cmsis-driver
小李做物联网1 天前
【单片机毕设】c24基于单片机stm32蓝牙温室大棚物联网毕业设计
stm32·单片机·嵌入式硬件·物联网