stm32h743单片机嵌入式学习笔记3-内存管理原理

版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎联系我qq2488890051 https://blog.csdn.net/kangkanglhb88008/article/details/89485821

都是要实现 2 个函数:malloc 和 free;malloc 函数用于内存申请,free 函数用于内存释放。

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 n
块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。
内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当
该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。
比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某
个指针。
内寸分配方向如图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内存
管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。

分配原理
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 项开
始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内
存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,
完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的 m 块空闲内存),则
返回 NULL 给 p,表示分配失败。
释放原理
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。free 函数先判断 p 指向的内
存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内
存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释
放,完成一次内存释放。

介绍一下 STM32H7 的内存分配情况

从上表可以看出,STM32H7 的内存分成:ITCM、DTCM、AXI SRAM、SRAM1~4 等几个
部分,其中地址连续的区域分成 5 大块:ITCM(64KB)、DTCM(128KB)、AXI SRAM(512KB)
、SRAM1~3(288KB)和 SRAM4(64KB), 因为内存管理的内存池,必须是地址连续的内存
空间,因此,STM32H7 内部内存需要 5 个内存池来管理。
另外,需要注意:

对于rt1052单片机,也有DTCM-RAM和ITCM-RAM,如下图:

火哥这个是1052的教程,如果指令不是在外存中执行,ITCM-RAM应该是不能用的,cpu用来加载指令了。但是如果是程序在外存时候,这个ITCM-RAM应该就可以用来分配变量了。但是原子的stm32h743教程把DTCM-RAM和ITCM-RAM都用做内存管理了,说明这两块内存都可以用,因为stm32h743有内部flash,程序肯定默认都是在这里执行的,所以不会复制到ITCM-RAM来执行,所以原子这里才可以把它用来做内存管理,或者直接使用也行,有两种方式:

1. 在keil中设置起始地址和大小即可。这里两个都可以同时勾选,这样单片机内部不连续的内存块(据说是不同内存块挂载不同速度的总线,而且可以并行读取,比如单片机设为复制程序到内存中的运行方式,DTCM-RAM和ITCM-RAM分别存的是数据和指令,这样cpu可以并行的同时取到指令的内容和操作数,提高效率,实现了正真的哈佛体系结构,具体可以看我这篇文章,计算机冯诺伊曼体系结构和哈佛体系结构区别和处理器性能评判标准)也能给编译器使用了,而且是通过自动分配变量的方式(即:int a;这样)进行使用的还能勾选外部sdram芯片内存作为程序的变量自动分配地方,但是一般不这么做,因为外部sdram访问速度远远低于内部sram速度,而且DTCM-RAM和ITCM-RAM挂载速度总线是400MHZ,但是空间小,只有64KB和128KB,而AXI-SRAM挂载的是200MHZ速度,虽然慢了一倍,但是容量是整个单片机里面最大的。所以综合来看,程序的keil设置这里就使用AXI-SRAM即可,虽然只有512KB,不是整个sram 1060KB,但是对于通常程序,也足够使用了。那么其他内存块的空间岂不是浪费了呢,不是的,在下图的还可以勾选上一个块来使用。

2. 除此之外,可以在程序代码中通过__attribute__关键字定义一个大数组就可以使用了,还可以加上自己喜欢的内存管理,哈哈

所以我们看到本实验如下图的AXI SRAM默认勾选为程序的编译器为全局变量的自动分配变量区(分配变量int a;的方式即可),那么我们定义一个全局大数组就不需要__attribute__关键字指定起始地址了,而是编译器自动分配到AXI SRAM的全局未初始化变量区了,当然这里定义的这个大数组太大,

#define MEM1_MAX_SIZE            448*1024                          //最大管理内存 448K,H7的AXI内存总共512KB

因为程序中可能还会定义其他全局变量和扣除在启动文件中分配的栈,堆的空间,具体关于这个解释,看我这篇文章,嵌入式处理器(单片机)启动文件学习(汇编)笔记,DCD和SPACE区别,全局变量,局部变量,程序的栈,堆,程序编译结果的占用空间意思

//内部 SRAM 内存池(AXI SRAM)
__align(64) u8 mem1base[MEM1_MAX_SIZE];


1,ITCM 和 DTCM 这两个内存块,仅 CPU 和 MDMA 可以直接访问,其他外设不可以直
接访问!但是也是可以分配
2,以太网的 DMA 描述符等必须是定义在 SRAM3 里面才可以正常工作,因此我们一般把
SRAM3(32KB)独立给以太网使用,并不用作内存管理!

//内存池(64 字节对齐)
//内部 SRAM 内存池(AXI SRAM)
__align(64) u8 mem1base[MEM1_MAX_SIZE];
//外部 SDRAM 内存池,前面 2M 给 LTDC 用了(1280*800*2)
__align(64) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0XC01F4000)));  // 因为必须是连续空间才能定义大数组,所以这就解释了为什么必须是连续内存才能成为一个内存管理
//内部 SRAM1+SRAM2 内存池
__align(64) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0x30000000)));
//内部 SRAM4 内存池
__align(64) u8 mem4base[MEM4_MAX_SIZE] __attribute__((at(0x38000000)));
//内部 DTCM 内存池
__align(64) u8 mem5base[MEM5_MAX_SIZE] __attribute__((at(0x20000000)));
//内部 ITCM 内存池
__align(64) u8 mem6base[MEM6_MAX_SIZE] __attribute__((at(0x00000000)));
//内存管理表
u32 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //内部 SRAM 内存池 MAP
u32 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0XC01F4000+
MEM2_MAX_SIZE))); //外部 SDRAM 内存池 MAP  // 我们可以看到内存块管理表也是存放在内存池里面的
u32 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0x30000000+
MEM3_MAX_SIZE))); //内部 SRAM1+SRAM2 内存池 MAP
u32 mem4mapbase[MEM4_ALLOC_TABLE_SIZE] __attribute__((at(0x38000000+
MEM4_MAX_SIZE))); //内部 SRAM4 内存池 MAP
u32 mem5mapbase[MEM5_ALLOC_TABLE_SIZE] __attribute__((at(0x20000000+
MEM5_MAX_SIZE))); //内部 DTCM 内存池 MAP
u32 mem6mapbase[MEM6_ALLOC_TABLE_SIZE] __attribute__((at(0x00000000+
MEM6_MAX_SIZE))); //内部 ITCM 内存池 MAP

那么也就是说这个程序得管理六个上述连续的内存块,如下代码:

//内存管理控制器
struct _m_mallco_dev
{
    void (*init)(u8);                    //初始化
    u16 (*perused)(u8);                      //内存使用率
    u8     *membase[SRAMBANK];                //内存池 管理SRAMBANK个区域的内存
    u32 *memmap[SRAMBANK];                 //内存管理状态表
    u8  memrdy[SRAMBANK];                 //内存管理是否就绪,这些成员都是数组,即都是6个表项
};

其中

//定义六个内存池
#define SRAMIN         0        //AXI内存池,AXI共512KB 
#define SRAMEX       1        //外部内存池(SDRAM),SDRAM共32MB
#define SRAM12        2        //SRAM1/2内存池,SRAM1+SRAM2,共256KB
#define SRAM4        3        //SRAM4内存池,SRAM4共64KB
#define SRAMDTCM     4        //DTCM内存池,DTCM共128KB,此部分内存仅CPU和MDMA(通过AHBS)可以访问!!!!
#define SRAMITCM     5        //ITCM内存池,DTCM共64 KB,此部分内存仅CPU和MDMA(通过AHBS)可以访问!!!!


#define SRAMBANK     6        //定义支持的SRAM块数.    

主函数的init这样写就行了:

    my_mem_init(SRAMIN);            //初始化内部内存池(AXI)
    my_mem_init(SRAMEX);            //初始化外部内存池(SDRAM)
    my_mem_init(SRAM12);            //初始化SRAM12内存池(SRAM1+SRAM2)
    my_mem_init(SRAM4);                //初始化SRAM4内存池(SRAM4)
    my_mem_init(SRAMDTCM);            //初始化DTCM内存池(DTCM)
    my_mem_init(SRAMITCM);            //初始化ITCM内存池(ITCM)

比如init函数的源码:

//内存管理初始化  
//memx:所属内存块
void my_mem_init(u8 memx)  
{  
    mymemset(mallco_dev.memmap[memx],0,memtblsize[memx]*4);    //内存状态表数据清零  
     mallco_dev.memrdy[memx]=1;                                //内存管理初始化OK 

就把各自的内存管理表啥的都清空为原始状态

在我们申请内存时候就这样调用即可:

p=mymalloc(sramx,2048);//申请2K字节

我们追踪看看底层关键函数源码:

//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址 
u32 my_mem_malloc(u8 memx,u32 size)  
{  
    signed long offset=0;  
    u32 nmemb;    //需要的内存块数  
    u32 cmemb=0;//连续空内存块数
    u32 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;//未找到符合分配条件的内存块  
}  
释放内存就这样用:

myfree(sramx,p);//释放内存,其原理是根据这个地址p找到应该对应的内存管理表项,表项的内容是一个非零的数,且数值大小就代表分配了多少块给这个p(而且这几块管理表项数值都是一样的,表示分配了多少块,那么无论从后向前扫描分配还是从前向后,都可以准确知道哪些块是被一次性分配的,哪些不是一起的),然后把这些表项都改为0(真正的这块内存块并不需要写0,因为不影响),就达到了回收这地址p所对应的内存目的。同理,sizeof(p)函数之所以能知道这个变量的所占的内存大小,也是因为这个原因,就找这几块管理表项数值都是一样的累加起来就得到大小了。然后 关键代码如下:

//释放内存(内部调用) 
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;  
u8 my_mem_free(u8 memx,u32 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;  //把这些表项都改为0
        }  
        return 0;  
    }else return 2;//偏移超区了.  
}  

//获取内存使用率
//memx:所属内存块
//返回值:使用率(扩大了10倍,0~1000,代表0.0%~100.0%)
u16 my_mem_perused(u8 memx)  
{  
    u32 used=0;  
    u32 i;  
    for(i=0;i<memtblsize[memx];i++)  
    {  
        if(mallco_dev.memmap[memx][i])used++;   // 实际上就是找到内存块对应的管理表项的值非零就代表此块被使用了
    } 
    return (used*1000)/(memtblsize[memx]);  
}  

猜你喜欢

转载自blog.csdn.net/kangkanglhb88008/article/details/89485821