300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 正点原子的内存管理_正点原子【STM32-F407探索者】第四十二章 内存管理实验

正点原子的内存管理_正点原子【STM32-F407探索者】第四十二章 内存管理实验

时间:2023-06-17 19:10:29

相关推荐

正点原子的内存管理_正点原子【STM32-F407探索者】第四十二章 内存管理实验

1)资料下载:点击资料即可下载

2)对正点原子Linux感兴趣的同学可以加群讨论:935446741

3)关注正点原子公众号,获取最新资料更新

上一章,我们学会了使用 STM32F4 驱动外部 SRAM,以扩展 STM32F4 的内存,加上 STM32F4 本身自带的 192K 字节内存,我们可供使用的内存还是比较多的。如果我们所用的内 存都像上一节的 testsram 那样,定义一个数组来使用,显然不是一个好办法。 本章,我们将学习内存管理,实现对内存的动态管理。本章分为如下几个部分:

42.1 内存管理简介

42.2 硬件设计

42.3 软件设计

42.4 下载验证

42.1 内存管理简介

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

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

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 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 个内存管理表项目的值都清零,标记释 放,完成一次内存释放。

关于分块式内存管理的原理,我们就介绍到这里。

42.2 硬件设计

本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0 用于申请内存,每次 申请 2K 字节内存。KEY1 用于写数据到申请到的内存里面。KEY2 用于释放内存。KEY_UP 用于切换操作内存区(内部 SRAM 内存/外部 SRAM 内存/内部 CCM 内存)。DS0 用于指示程 序运行状态。本章我们还可以通过 USMART 调试,测试内存管理函数。

本实验用到的硬件资源有:

1) 指示灯 DS0

2) 四个按键

3) 串口

4) TFTLCD 模块

5) XM8A51216

这些我们都已经介绍过,接下来我们开始软件设计。

42.3 软件设计

打开本章实验工程可以看到,我们新增了 MALLOC 分组,同时在分组中新建了文件

malloc.c 以及头文件 malloc.h。 内存管理相关的函数和定义主要是在这两个文件中。

打开 malloc.c 文件,代码如下:

//内存池(32 字节对齐)

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

__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));

//外部 SRAM 内存池

__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X10000000)));

//内部 CCM 内存池

//内存管理表

u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];

//内部 SRAM 内存池 MAP

u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000

+MEM2_MAX_SIZE)));

//外部 SRAM 内存池 MAP

u16 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0X10000000

+MEM3_MAX_SIZE)));

//内部 CCM 内存池 MAP

//内存管理参数

const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,

MEM2_ALLOC_TABLE_SIZE,MEM3_ALLOC_TABLE_SIZE}; //内存表大小

const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE,

MEM3_BLOCK_SIZE};

//内存分块大小

const u32 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,

//内存管理未就绪

};

//复制内存

//*des:目的地址

//*src:源地址

//n:需要复制的内存长度(字节为单位)

void mymemcpy(void *des,void *src,u32 n)

{

u8 *xdes=des;

u8 *xsrc=src;

while(n--)*xdes++=*xsrc++;

}

//设置内存

//*s:内存首地址

//c :要设置的值

//count:需要设置的内存大小(字节为单位)

void mymemset(void *s,u8 c,u32 count)

{

u8 *xs = s;

while(count--)*xs++=c;

}

//内存管理初始化

//memx:所属内存块

void my_mem_init(u8 memx)

{

mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//内存状态表数据清零

mymemset(mallco_dev.membase[memx], 0,memsize[memx]); //内存池所有数据清零

mallco_dev.memrdy[memx]=1;

//内存管理初始化 OK

}

//获取内存使用率

//memx:所属内存块

//返回值:使用率(0~100)

u8 my_mem_perused(u8 memx)

{

u32 used=0;u32 i;

for(i=0;i

return (used*100)/(memtblsize[memx]);

}

//内存分配(内部调用)

//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

//标注内存块非空

{

mallco_dev.memmap[memx][offset+i]=nmemb;

}

return (offset*memblksize[memx]);//返回偏移地址

}

}

return 0XFFFFFFFF;//未找到符合分配条件的内存块

}

//释放内存(内部调用)

//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

{

int index=offset/memblksize[memx];

//偏移所在内存块号码

int nmemb=mallco_dev.memmap[memx][index]; //内存块数量

for(i=0;i

return 0;

}else return 2;//偏移超区了.

}

//释放内存(外部调用)

//memx:所属内存块

//ptr:内存首地址

void myfree(u8 memx,void *ptr)

{

u32 offset;

if(ptr==NULL)return;//地址为 0.

offset=(u32)ptr-(u32)mallco_dev.membase[memx];

my_mem_free(memx,offset); //释放内存

}

//分配内存(外部调用)

//memx:所属内存块

//size:内存大小(字节)

//返回值:分配到的内存首地址.

void *mymalloc(u8 memx,u32 size)

{

u32 offset;

offset=my_mem_malloc(memx,size);

if(offset==0XFFFFFFFF)return NULL;

else return (void*)((u32)mallco_dev.membase[memx]+offset);

}

//重新分配内存(外部调用)

//memx:所属内存块

//*ptr:旧内存首地址

//size:要分配的内存大小(字节)

//返回值:新分配到的内存首地址.

void *myrealloc(u8 memx,void *ptr,u32 size)

{

u32 offset;

offset=my_mem_malloc(memx,size);

if(offset==0XFFFFFFFF)return NULL;

else

{

mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size);

//拷贝旧内存内容到新内存

myfree(memx,ptr);

//释放旧内存

return (void*)((u32)mallco_dev.membase[memx]+offset); //返回新内存首地址

}

}

这里,我们通过内存管理控制器 mallco_dev 结构体(mallco_dev 结构体见 malloc.h),实现 对三个内存池的管理控制。为甚

首先,是内部 SRAM 内存池,定义为:

__align(32) u8 mem1base[MEM1_MAX_SIZE];

然后,是外部 SRAM 内存池,定义为:

__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));

最后,是内部 CCM 内存池,定义为:

__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X10000000)));

这里之所以要定义成 3 个,是因为这三个内存区域的地址都不一样,STM32F4 内部内存分 为两大块:1,普通内存(又分为主要内存和辅助内存,地址从:0X2000 0000 开始,共 128KB), 这部分内存任何外设都可以访问。2,CCM 内存(地址从:0X1000 0000 开始,共 64KB),这 部分内存仅 CPU 可以访问,DMA 之类的不可以直接访问,使用时得特别注意!! 而外部 SRAM,地址是从 0X6800 0000 开始的,共 1024KB。

所以,这样总共有 3 部分内 存,而内存池必须是连续的内存空间,才可以,这样 3 个内存区域,就有 3 个内存池,因此, 分成了 3 块来管理。

其中,MEM1_MAX_SIZE、MEM2_MAX_SIZE 和 MEM3_MAX_SIZE 为在 malloc.h 里面 定义的内存池大小,外部 SRAM 内存池指定地址为 0X6800 0000,也就是从外部 SRAM 的首地 址开始的,CCM 内存池从 0X1000 0000 开始,同样是从 CCM 内存的首地址开始的。但是,内 部 SRAM 内存池的首地址则由编译器自动分配。__align(32)定义内存池为 32 字节对齐,以适应 各种不同场合的需求。

此部分代码的核心函数为:my_mem_malloc 和 my_mem_free,分别用于内存申请和内存释 放。思路就是我们在 42.1 接所介绍的那样分配和释放内存,不过这两个函数只是内部调用,外 部调用我们使用的是mymalloc和myfree两个函数。其他函数我们就不多介绍了,保存malloc.c, 然后,打开 malloc.h,代码如下:

#ifndef __MALLOC_H

#define __MALLOC_H

#include "stm32f4xx.h"#ifndef NULL

#define NULL 0

#endif

//定义三个内存池

#define SRAMIN

0

//内部内存池

#define SRAMEX 1

//外部内存池

#define SRAMCCM 2

//CCM 内存池(此部分 SRAM 仅仅 CPU 可以访问!!!)

#define SRAMBANK 3

//定义支持的 SRAM 块数.

//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 的内存池处于外部 SRAM 里面

#define MEM2_BLOCK_SIZE

32

//内存块大小为 32 字节

#define MEM2_MAX_SIZE

960 *1024

//最大管理内存 960K

#define MEM2_ALLOC_TABLE_SIZE

MEM2_MAX_SIZE/MEM2_BLOCK_SIZE

//内存表大小

//mem3 内存参数设定.mem3 处于 CCM,用于管理 CCM(特别注意,这部分 SRAM,仅 CPU 可

//以访问!!)

#define MEM3_BLOCK_SIZE

32

//内存块大小为 32 字节

#define MEM3_MAX_SIZE

60 *1024

//最大管理内存 60K

#define MEM3_ALLOC_TABLE_SIZE

MEM3_MAX_SIZE/MEM3_BLOCK_SIZE

//内存表大小

//内存管理控制器

struct _m_mallco_dev

{

void (*init)(u8);

//初始化

u8 (*perused)(u8);

//内存使用率

u8 *membase[SRAMBANK];

//内存池 管理 SRAMBANK 个区域的内存

u16 *memmap[SRAMBANK];

//内存管理状态表

u8 memrdy[SRAMBANK];

//内存管理是否就绪

};

extern struct _m_mallco_dev mallco_dev; //在 mallco.c 里面定义

void mymemset(void *s,u8 c,u32 count);

//设置内存

void mymemcpy(void *des,void *src,u32 n);//复制内存

void my_mem_init(u8 memx);

//内存管理初始化函数(外/内部调用)

u32 my_mem_malloc(u8 memx,u32 size); //内存分配(内部调用)

u8 my_mem_free(u8 memx,u32 offset);

//内存释放(内部调用)

u8 my_mem_perused(u8 memx);

//获得内存使用率(外/内部调用)

//用户调用函数

void myfree(u8 memx,void *ptr);

//内存释放(外部调用)

void *mymalloc(u8 memx,u32 size);

//内存分配(外部调用)

void *myrealloc(u8 memx,void *ptr,u32 size);//重新分配内存(外部调用)

#endif

这部分代码,定义了很多关键数据,比如内存块大小的定义:MEM1_BLOCK_SIZE、

MEM2_BLOCK_SIZE 和 MEM3_BLOCK_SIZE,都是 32 字节。内存池总大小,内部 SRAM 内

存池大小为 100K,外部 SRAM 内存池大小为 960K,内部 CCM 内存池大小为 60K。

MEM1_ALLOC_TABLE_SIZE、MEM2_ALLOC_TABLE_SIZE 和 MEM3_ALLOC_TABLE_

SIZE,则分别代表内存池 1、2 和 3 的内存管理表大小

从这里可以看出,如果内存分块越小,那么内存管理表就越大,当分块为 2 字节 1 个块的

时候,内存管理表就和内存池一样大了(管理表的每项都是 u16 类型)。显然是不合适的,我们

这里取 32 字节,比例为 1:16,内存管理表相对就比较小了。

其他就不多说了,大家自行看代码理解就好。接下来我们看看主函数代码:

int main(void)

{

u8 key; u8 i=0; u8 *p=0;u8 *tp=0;

u8 paddr[18];

//存放 P Addr:+p 地址的 ASCII 值

u8 sramx=0;

//默认为内部 sram

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(336,8,2,7);

//设置时钟,168Mhz

delay_init(168);

//初始化延时函数

uart_init(115200);

//初始化 USART

usmart_dev.init(84);

//初始化 USMART

LED_Init();

//初始化 LED

KEY_Init();

//初始化 KEY

LCD_Init(); //初始化 LCD

SRAM_Init();

//初始化外部 SRAM

my_mem_init(SRAMIN);

//初始化内部内存池

my_mem_init(SRAMEX);

//初始化外部内存池

my_mem_init(SRAMCCM);

//初始化 CCM 内存池

POINT_COLOR=RED;//设置字体为红色

LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");

LCD_ShowString(30,70,200,16,16,"MALLOC TEST");

LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");

LCD_ShowString(30,110,200,16,16,"/5/15");

LCD_ShowString(30,130,200,16,16,"KEY0:Malloc KEY2:Free");

LCD_ShowString(30,150,200,16,16,"KEY_UP:SRAMx KEY1:Read");

POINT_COLOR=BLUE;//设置字体为蓝色

LCD_ShowString(30,170,200,16,16,"SRAMIN");

LCD_ShowString(30,190,200,16,16,"SRAMIN USED: %");

LCD_ShowString(30,210,200,16,16,"SRAMEX USED: %");

LCD_ShowString(30,230,200,16,16,"SRAMCCM USED: %");

while(1)

{

key=KEY_Scan(0);//不支持连按

switch(key)

{

case 0://没有按键按下

break;

case KEY0_PRES: //KEY0 按下

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

if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入内容

break;

case KEY1_PRES: //KEY1 按下

if(p!=NULL)

{

sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容

LCD_ShowString(30,270,200,16,16,p);//显示 P 的内容

}

break;

case KEY2_PRES: //KEY2 按下

myfree(sramx,p); //释放内存

p=0;

//指向空地址

break;

case WKUP_PRES:

//KEY UP 按下

sramx++;

if(sramx>2)sramx=0;

if(sramx==0)LCD_ShowString(30,170,200,16,16,"SRAMIN ");

else if(sramx==1)LCD_ShowString(30,170,200,16,16,"SRAMEX ");

else LCD_ShowString(30,170,200,16,16,"SRAMCCM");

break;

}

if(tp!=p)

{

tp=p;

sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);

LCD_ShowString(30,250,200,16,16,paddr); //显示 p 的地址

if(p)LCD_ShowString(30,270,200,16,16,p);//显示 P 的内容

else LCD_Fill(30,270,239,266,WHITE);

//p=0,清除显示

}

delay_ms(10);

i++;

if((i%20)==0)//DS0 闪烁.

{

LCD_ShowNum(30+104,190,my_mem_perused(SRAMIN),3,16);//显示使用率

LCD_ShowNum(30+104,210,my_mem_perused(SRAMEX),3,16);//显示使用率

LCD_ShowNum(30+104,230,my_mem_perused(SRAMCCM),3,16);//使用率

LED0=!LED0;

该部分代码比较简单,主要是对 mymalloc 和 myfree 的应用。不过这里提醒大家,如果对 一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理 所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定 记得,申请的内存在用完以后,一定要释放。

42.4 下载验证

在代码编译成功之后,我们通过下载代码到 ALIENTEK 探索者 STM32F4 开发板上,得到

如图 42.4.1 所示界面:图 42.4.1 程序运行效果图

可以看到,所有内存的使用率均为 0%,说明还没有任何内存被使用,此时我们按下 KEY0,

就可以看到内部 SRAM 内存被使用 2%了,同时看到下面提示了指针 p 所指向的地址(其实就 是被分配到的内存地址)和内容。多按几次 KEY0,可以看到内存使用率持续上升(注意对比 p 的值,可以发现是递减的,说明是从顶部开始分配内存!),此时如果按下 KEY2,可以发现 内存使用率降低了 2%,但是再按 KEY2 将不再降低,说明“内存泄露”了。这就是前面提到 的对一个指针多次申请内存,而之前申请的内存又没释放,导致的“内存泄露”。

按 KEY_UP 按键,可以切换当前操作内存(内部 SRAM 内存/外部 SRAM 内存/内部 CCM 内存),KEY1 键用于更新 p 的内容,更新后的内容将重新显示在 LCD 模块上面。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。