单片机内存管理

news2024/11/17 7:17:55

单片机内存管理

1、随机存储器

RAM是随机存储器,读写速度快,但掉电以后数据会丢失。它分为SRAM(静态RAM)和DRAM(动态RAM)。SRAM无需刷新就可以保存数据;DRAM需要不断刷新才可以保存数据。在CPU内部的RAM,就叫内部RAM,在CPU外部的RAM,就叫外部RAM。单片机应用中,一般很少扩展外部RAM。

2、单片机内存由哪几部分构成

单片机内存位于RAM中,它被分成四个区:静态存储区、栈区、堆区和未用区

静态存储区用来存放全局变量和static型变量,它在程序编译的时候就已经被分配好了。

用来保护现场和恢复现场的栈很多,主要有:函数栈和任务栈。在使用操作系统时,我们会申请一块数组用作任务栈,任务栈位于静态存储区,其实函数栈也位于静态存储区,只是我们硬要把他们分开叫而已。严格来讲,内存只有堆区静态存储区两种

栈区和堆区的大小由软件工程师设置。例如在STM32F103的启动文件中,有两个宏定义:Stack_Size和Heap_Size,其中Stack_Size用来设置栈区的大小,Heap_Size用来设置

堆区的大小,它们在startup_stm32f10x_hd.s文件中。格式如下:

Stack_Size   EQU     0x00000400

Heap_Size   EQU     0x00000200

注意:如果没有使用标准库的malloc,这里Heap_Size可以设置为0。例如:在使用其他操作系统或自定义的malloc.c文件时,都需要定义一个数组用作堆区,用作内存申请和释放。

3、堆区管理

下面主要介绍我的malloc.c文件,了解是如何进行堆管理。在单片机应用中,一般很少扩展外部RAM,通常会根据实际情况,选择合适的CPU来满足设计需求。堆的生长方向,本程序是向下的。

//定义两个内存池

#define InternalRAM          0    //内部堆区内存池

#define ExternalRAM       1    //外部堆区内存池

#define RAMBankNumber   2    //堆区种类为2

/内部堆定义开始/

#define InternalRAM_BlockSize    32

//定义内部堆区最小的数据块为32字节对齐

#define InternalRAM_TotalNumberOfBytes  30*1024  //定义内部堆区的大小为30K

#define InternalRAM_NumberOfBlock   InternalRAM_TotalNumberOfBytes/InternalRAM_BlockSize

//定义内部堆区中块的总数量为1280

__align(32) u8 InternalRAM_Array[InternalRAM_TotalNumberOfBytes];

//定义内部堆区内存池首地址为InternalRAM_Array,共分配InternalRAM_TotalNumberOfBytes个字节空间,并指定按照32位对齐。

u16 InternalRAM_MemoryStatusTableArray[InternalRAM_NumberOfBlock ];

//内部堆区的内存状态表

//InternalRAM_MemoryStatusTableArray[i]=0表示块i是空闲的,可以使用;

/内部堆定义结束/

/外部堆定义开始/

#define ExternalRAM_BlockSize  InternalRAM_BlockSize

//定义外部堆区最小的数据块为32字节对齐,保证内部堆和外部堆的大小相同

#define ExternalRAM_TotalNumberOfBytes  960 *1024 //定义外部堆区的大小为1M

#define ExternalRAM_NumberOfBlock   ExternalRAM_TotalNumberOfBytes/ExternalRAM_BlockSize

//定义外部SRAM中块的总数量为30720

__align(32) u8 ExternalRAM_Array[ExternalRAM_TotalNumberOfBytes]  __attribute__((at(0X68000000)));

//定义外部堆区的内存池首地址为ExternalRAM_Array,共分配ExternalRAM_TotalNumberOfBytes个字节空间,并指定按照32位对齐,进行数据访问

//定义外部内存内存池的物理首地址为0X68000000

u16 ExternalRAM_MemoryStatusTableArray[ExternalRAM_NumberOfBlock] __attribute__((at(0X68000000+ ExternalRAM_TotalNumberOfBytes)));

//外部堆区的内存状态表,其首地址为0X68000000+ ExternalRAM_TotalNumberOfBytes

// ExternalRAM_MemoryStatusTableArray[i]=0表示块i是空闲的,可以使用;

/外部堆定义结束/

struct  _m_mallco_dev

{//内存管理控制器

  void (*init)(u8);             //初始化函数

  u8   (*perused)(u8);        //内存使用率统计函数

  u8   *membase[RAMBankNumber];

//指针数组用来存放内存池数组首地址,内存池数量为RAMBankNumber

  u16  *memmap[RAMBankNumber];

//指针数组用来存放内存管理状态表, 内存管理状态表数量为RAMBankNumber

  u8  memrdy[RAMBankNumber];   //内存管理是否就绪

};

//extern struct  _m_mallco_dev   mallco_dev;

//内存管理参数

const u32 NumberOfMyBlock[RAMBankNumber]={ InternalRAM_NumberOfBlock, ExternalRAM_NumberOfBlock };

//记录内部堆和外部堆中分别含有块的总数量

const u32 MyBlockSize[RAMBankNumber]={ InternalRAM_BlockSize, ExternalRAM_BlockSize };

//记录内部堆和外部堆中的块的大小

const u32 MyTotalNumberOfBytes[RAMBankNumber]={ InternalRAM_TotalNumberOfBytes, ExternalRAM_TotalNumberOfBytes };

//记录内部堆和外部堆的内存池大小

//函数功能:src为首地址的存储块,复制前n个字节到des为首地址的存储块中

//*des:目的地址

//*src:源地址

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

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

{

  u8 *xdes=des;

  u8 *xsrc=src;

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

}

//函数功能:s为首地址的存储块的前count个字节全部设置为c的值

//*s:内存首地址

//c :要设置的值

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

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

{

  u8 *xs = s;

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

}

//函数功能: 清除内存状态表和堆区的内存池

//memx= InternalRAM,初始化内部堆区;memx= ExternalRAM,初始化外部堆区

void MyMemoryInit(u8 memx)

{

  MyMemorySet( mallco_dev.memmap[memx], 0, NumberOfMyBlock[memx]*2 );

         //将堆区的内存状态表数组清零,NumberOfMyBlock[]表示块的总数量

//InternalRAM_MemoryStatusTableArray[]ExternalRAM_MemoryStatusTableArray[]存储的是双字节

//NumberOfMyBlock[memx]也就是双字节,所以这里乘以2;

  MyMemorySet( mallco_dev.membase[memx], 0, MyTotalNumberOfBytes[memx] );

         //将堆区的内存池所有数据清零

// InternalRAM_Array[]ExternalRAM_Array[]存储的是单字节,

//mallco_dev.membase[memx]也就是单字节存储区

  mallco_dev.memrdy[memx]=1; //内存管理初始化OK

}

//函数功能:

//memx= InternalRAM,计算内部堆区的使用率;

//memx= ExternalRAM,计算外部堆区的使用率

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

u8 Get_MyMemoryUsed(u8 memx)

{

  u32 used=0;

  u32 i;

  for(i=0;i<NumberOfMyBlock[memx];i++)

  {

if( mallco_dev.memmap[memx][i] ) used++;

//计算堆区内存池中空闲块的数量,

//堆区内存池中块的总数量保存在NumberOfMyBlock[memx]

//mallco_dev.memmap[0][y]=0表示内部堆的块y是空闲的

//mallco_dev.memmap[1][y]=0表示外部堆的块y是空闲的

  }

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

}

//内存管理控制器,创建结构变量时,就开始初始化一次

struct _m_mallco_dev   mallco_dev=

{

         MyMemoryInit,       //调用MyMemoryInit(),内存初始化

         Get_MyMemoryUsed,   //调用Get_MyMemoryUsed(),统计内存使用率

         InternalRAM_Array,  //传入InternalRAM_Array[]数组首地址

ExternalRAM_Array,  //传入ExternalRAM_Array[]数组首地址

         InternalRAM_MemoryStatusTableArray,

//传入InternalRAM_MemoryStatusTableArray[]数组首地址

ExternalRAM_MemoryStatusTableArray,    

//传入ExternalRAM_MemoryStatusTableArray[]数组首地址

         0,                //内部堆区管理未就绪

0                //外部堆区管理未就绪

};

//函数功能:

//memx= InternalRAM,从内部堆区内存池中分配size个字节;

//memx= ExternalRAM,从外部堆区内存池中分配size个字节;

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

//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址

u32 MyMemoryMalloc(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/MyBlockSize[memx];

//计算需要多少个整块, MyBlockSize[memx]为块的大小

  if( size%MyBlockSize[memx] ) nmemb++;

//剩余的字节空间不满一个块的,则按一个整块操作

///MyBlockSize[memx]为块的大小,NumberOfMyBlock[memx]为块的总数量

  for( offset=NumberOfMyBlock[memx]-1;offset>=0;offset-- )//搜索内存池,offset为块的号码

  {//堆区按照向下生成方式分配空间, NumberOfMyBlock[]表示块的总数量

if( !mallco_dev.memmap[memx][offset] ) cmemb++;//若块空间空闲,cmemb1

         else cmemb=0; //若连续空闲块数量小于nmemb,则cmemb=0

    if(cmemb==nmemb) //找到了nmemb个连续空闲块

    {

      for(i=0;i<nmemb;i++) //标注内存块非空

      {

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

//”nmemb个连续空闲块对应的内存状态表设置为nmemb

//连续的内存状态表的值相同,表示为同批次分配

      }

      return ( offset*MyBlockSize[memx] );//返回分配到的内存池偏移地址

    }

  }

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

}

//函数功能:释放内存(内部调用)

//memx= InternalRAM,从内部堆区内存池中释放偏移地址为offset的数据块;

//memx= ExternalRAM,从外部堆区内存池中释放偏移地址为offset的数据块;

//offset:内存地址偏移

//返回值:0,释放成功;1,释放失败;

u8 MyMemoryFree(u8 memx,u32 offset)

{

  int i;

  if( !mallco_dev.memrdy[memx] )//未初始化,先执行初始化

  {

    mallco_dev.init(memx);//初始化内存池

    return 1;//未初始化

  }

  if(offset< MyTotalNumberOfBytes[memx])//偏移在内存池内

  {

    int index=offset/MyBlockSize[memx]; //计算偏移地址offset在内存块中的号码

    int nmemb=mallco_dev.memmap[memx][index]; //读取要释放的块数量

    for(i=0;i<nmemb;i++)

    {

      mallco_dev.memmap[memx][index+i]=0;

//”nmemb个连续空闲块对应的内存状态表设置为0

    }

    return 0;//释放内存成功

  }

  else return 2;//偏移超区了

}

//函数功能:分配内存(外部调用)

//memx= InternalRAM,从内部堆区内存池中分配size个字节;

//memx= ExternalRAM,从外部堆区内存池中分配size个字节;

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

void * MyMalloc(u8 memx,u32 size)

{

  u32 offset;

  offset= MyMemoryMalloc(memx,size);//读分配size个字节空间的偏移地址

  if(offset==0XFFFFFFFF)//没有分配到数据块

 return NULL;

  else //分配到数据

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

//返回分配到的数据块首地址

}

//函数功能:释放内存(外部调用)

//memx= InternalRAM,从内部堆区内存池中释放首移地址为ptr的字节空间;

//memx= ExternalRAM,从外部堆区内存池中释放首移地址为ptr的字节空间;

//释放的块数为在内存状态表中

//ptr:内存首地址

void MyFree(u8 memx,void *ptr)

{

  u32 offset;

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

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

  //计算偏移地址

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

}

//函数功能:

//ptr为首地址的前size个字节拷贝到新分配的内存中,再释放ptr为首地址的内存

//memx= InternalRAM,从内部堆区内存池中分配size个字节;

//memx= ExternalRAM,从外部堆区内存池中分配size个字节;

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

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

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

{

  u32 offset;

  offset=MyMemoryMalloc(memx,size);//读分配size个字节空间的偏移地址

  if(offset==0XFFFFFFFF)

return NULL;

  else

  {

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

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

    myfree(memx,ptr);//释放旧内存

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

  }

}

//函数功能:动态分配内存,

//注意:为了和FreeRTOS兼容定义为pvPortMalloc(u32 size)

/*

在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c,系统所有总的堆大小,体现在ZI-DATA

FreeRTOSConfig.h中,有个宏定义:#define configTOTAL_HEAP_SIZE   ((size_t)(33*1024))

*/

void *pvPortMalloc(u32 size)                     

{

         return (void*)MyMalloc(InternalRAM,size);

}

//函数功能:释放内部RAM的内存

//注意:为了和FreeRTOS兼容定义为vPortFree()

//在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c

/*

在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c,系统所有总的堆大小,体现在ZI-DATA

FreeRTOSConfig.h中,有个宏定义:#define configTOTAL_HEAP_SIZE   ((size_t)(33*1024))

*/

void vPortFree(void* mf)

{

         myfree(InternalRAM,mf);

}

u8 Myused;

int main(void)

{

         u8 *p;

         MyMemoryInit(InternalRAM); //初始化内部内存池

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         p=pvPortMalloc(15*1024);

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         vPortFree(p);

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         while(1)

         {

         }

}

4、函数栈的管理

函数栈也中断栈,或叫硬件栈,书本上没有严格的定义。栈区用来存放局部变量和一些寄存器数据。局部变量在函数内部,其存储空间位于栈中。当进入函数时,会对根据局部变量需求,在栈上申请一段内存空间,供局部变量使用。当局部变量生命周期结束后,在栈上释放。在C语言程序中,是由编译器系统完成申请和释放的。CPU栈的增长方向,通常是向下的。

在C语言程序中,“函数A”调用“函数B”时,需要将一些寄存器数据和“函数A”中的局部变量压入到“指定的RAM”中入栈代码是由编译器系统完成的,这叫现场保护;接着再执行“函数B”;“函数B”被执行完毕后再回到“函数A”,此时需要将从“指定的RAM”中把“以前压入栈中的数据”返回给寄存器和“函数A”中的局部变量出栈代码是由编译器系统完成的,叫恢复现场。这个“指定的RAM”,叫“函数栈”或“中断栈”。在C语言中,入栈和出栈是由编译器系统自动完成的,但在汇编语言中,入栈和出栈代码就需要人工完成。见下图:

5、任务栈的管理

任务栈需要结合具体的操作系统,说明会更有效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634517.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法模板(3):搜索(4):高等图论

高等图论 有向图的强连通分量 相关概念 强连通分量&#xff1a;Strongly Connected Component (SCC).对于一个有向图顶点的子集 S S S&#xff0c;如果在 S S S 内任取两个顶点 u u u 和 v v v&#xff0c;都能找到一条 u u u 到 v v v 的路径&#xff0c;那么称 S S…

JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节

JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节 JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节 文章目录 JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节前言Java对象创建的流程步骤包括哪些&#xff1f;总结 前言 JVM零…

【云原生 | 53】Docker三剑客之Docker Compose应用案例一:Web负载均衡

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

基于Echarts构建停车场数据可视化大屏(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【部署LVS-DR 群集】

目录 一、DR模式 LVS负载均衡群集1、数据包流向分析2、DR 模式的特点 二、DR模式 LVS负载均衡群集部署1、1.配置负载调度器&#xff08;192.168.80.30&#xff09;&#xff08;1&#xff09;配置虚拟 IP 地址&#xff08;VIP&#xff1a;192.168.102.188&#xff09;&#xff0…

《设计模式》之装饰器模式

文章目录 1、定义2、动机3、类结构4、优缺点5、注意事项6、总结7、代码实现(C) 1、定义 动态&#xff08;组合&#xff09;地给一个对象增加一些额外的职责。就增加功能而言&#xff0c;Decorator模式比生成子类&#xff08;继承&#xff09;更为灵活&#xff08;消除重复代码…

PPT中这8个隐藏技巧-掌握了马上让你幸福感满满

开篇 一个好的PPT需要精雕细琢。即使我们使用了AIGC特别是时下流行的用GPT书写大纲,然后把大纲内的内容放到一些自动GC PPT内容的生成器里生成后的PPT其实也不是马上可以拿来用的。工作上一份大领导、公司、集团级别的PPT不可能90%使用GPT GC生成就可以直接交付的。比如说我们…

Trie树模板与应用

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/algorithms-notes 】或者公众号【AIShareLab】回复 算法笔记 也可获取。 文章目录 Trie树&#xff08;字典树&#xff09;基本思想例题 Trie字符串统计code关于idx的理解 模板总结应用 最大异或对分…

初探BERTPre-trainSelf-supervise

初探Bert 因为一次偶然的原因&#xff0c;自己有再次对Bert有了一个更深层地了解&#xff0c;特别是对预训练这个概念&#xff0c;首先说明&#xff0c;自己是看了李宏毅老师的讲解&#xff0c;这里只是尝试进行简单的总结复述并加一些自己的看法。 说Bert之前不得不说现在的…

ansible远程执行指令,/bin/sh: java: command not foundnon-zero return code

问题描述&#xff1a;ansible远程执行指令&#xff0c;初选指令加载不全&#xff0c; [rootVM-0-6-centos ~]# ansible all -m shell -a "java -version" 10.206.0.15 | FAILED | rc127 >> /bin/sh: java: command not foundnon-zero return code 解决方案&a…

C++(8):IO 库

IO 类 IO 库类型和头文件 iostream 定义了用于读写流的基本类型&#xff0c;fstream 定义了读写命名文件的类型&#xff0c;sstream 定义了读写内存 string 对象的类型。 其中带 w 前缀的类型用来操作宽字符语言 (wchar_t)。宽字符版本的类型和函数前都有一个 w&#xff0c;如…

SAP从入门到放弃系列之PP/DS-part1

翻译一篇大佬文章&#xff0c;了解一下PPDS前世今生和产品功能出现的业务背景。虽然是15年的&#xff0c;但经典永流传~~~&#xff0c;感谢大佬的文章。 原文地址&#xff1a; #S4HANA 1610 use case series: 9a – Production Planning and Detailed Scheduling – PP/DS (b…

【MySQL学习笔记】子查询与联结(连接)

1.子查询 将一条select语句返回的结果用于另一条select语句的where子句中。 执行时&#xff0c;先执行子查询&#xff0c;再执行主查询。 select sid from sc where cid in (select cid from course where cname数据库应用技术);子查询一般与 IN 操作符结合使用&#xff0…

《微服务实战》 第三十二章 微服务链路跟踪-sleuth zipkin

前言 大型分布式微服务系统中&#xff0c;一个系统被拆分成N多个模块&#xff0c;这些模块负责不同的功能&#xff0c;组合成一套系统&#xff0c;最终可以提供丰富的功能。在这种分布式架构中&#xff0c;一次请求往往需要涉及到多个服务服务之间的调用错综复杂&#xff0c;对…

Lenovo Yoga-710-14IKB电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板Lenovo Yoga 710 (14") - 14IKB (without dGPU) 处理器Intel i5-7200U (4) 2.50GHz (IKBL)已驱动 内存48 GB ( 海盗船 DDR4 3200…

web worker创建多个 JavaScript 线程 (使用GTP写的文章)

前言 最近在优化公司的一个项目&#xff0c;使用的就是web worker去优化&#xff0c;做了那些优化&#xff0c;一个是状态的优化&#xff0c;&#xff08;通信的状态实时更新&#xff0c;以前的做法是做个定时任务实时获取它的状态&#xff0c;然后让它在页面渲染&#xff0c;这…

【Linux】 -- TCP协议 (一)

TCP协议 Tcp协议可靠性冯诺依曼体系结构 TCP的协议格式序号与确认序号窗口大小六个标志位 确认应答机制 &#xff08;ACK&#xff09;超时重传机制连接管理机制 Tcp协议 TCP全称为 “传输控制协议”&#xff08;Transmission Control Protocol&#xff09; TCP协议被广泛应用…

[linux_C语言_udp的多种实现方法及网络调试中遇到的问题]

linux_C语言_udp的多种实现方法 最基本的方式(不用组播不用sigio信号不使能广播属性)接收端发送端 使用SIGIO信号的方式(使用sigio信号使用广播使能属性)服务端客户端 使用组播模式服务端客户端 tcp和udp的使用区别调试中遇到的问题所有源码下载点这~~ 最基本的方式(不用组播不…

Unix/Linux编程:UDS 流(Stream)

〇、前言 socket 是一种 IPC &#xff08;Inter-Process Communication&#xff0c;进程间通信&#xff09;方法&#xff0c;它允许位于同一主机&#xff08;计算机&#xff09;或使用网络连接起来的不同主机上的应用程序之间交换数据。通过使用Socket&#xff0c;开发人员可以…

【C++】——栈和队列(stack、queue)及优先队列(priority_queue)的介绍和模拟实现

文章目录 1. 前言2. 容器适配器2.1 容器适配器的介绍2.2 STL标准库中stack和queue的底层结构2.3 deque的简单介绍2.4 deque的缺陷2.5 为什么选择deque作为stack和queue的底层默认容器 3. stack3.1 stack的介绍3.2 stack的使用3.3 stack模拟实现 4. queue4.1 queue的介绍4.2 que…