• GD32(4)存储管理


    微控制器内存介绍

           大部分微控制器内部中只有Flash和SRAM两种存储器,Flash中会存储代码以及const定义的常量(除了局部非静态常量,即函数内定义的非static常量,emmm我也很奇怪)。SRAM中则存储所有的变量以及局部非静态常量。通过代码可能更好的理解这一点:

      const static int si_c ;    //在Flash中存储
      static int si;             //在SRAM中存储
      const int i_c;             //在Flash中存储 
      int i;                     //在SRAM中存储
    
    void num(void)
    {
      const static int sj_c ;    //在Flash中存储
      static int sj;             //在SRAM中存储
      const int j_c;             //在SRAM中存储
      int j;                     //在SRAM中存储
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    要验证上述类型的变量存储位置,可以定义后通过printf函数打印其地址。

           除了以上方式会直接使用到微控制器内部存储器外,还可以通过malloc函数动态分配内存,通过malloc函数分配的内存,同样是位于微控制器内部Flash中,如下所示:

    #include "stdlib.h"
    
    void num(void)
    {
      int* num;
      num = (int*)malloc(50);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

           一般来说,对微控制器的内存应用到此为止,通过malloc函数能基本运用到所有内存(SRAM一般较小,存储变量即可),但对更大的数据量则需要外部存储器进行存储,外部Flash、SRAM乃至SDRAM芯片可以存储大量数据,据此也引发了相应的问题:微控制器难以对外部存储器芯片进行有效的空间管理,即使使用EXMC、FSMC等接口,也只是对访问比较方便而已,因此需要通过编程,实现对各个存储器的管理。

    内存管理原理

           内存管理本质上是建立1张内存管理表(当然也可以有其它方法),类似NAND Flash管理中的FTL层,将表内的某一项对于实际内存的1小块,当通过设定的API函数申请内存时,内存管理表中对应项被赋值,同时实际内存的对应的块被分配,如下图所示。

    在这里插入图片描述

           在存储管理中,有以下几个主要变量、宏定义

    #define MemorySize  512*1024//外部存储块总大小
    #define BlockNum  512       //将外部存储块分为512块,对应内存管理表第1项至第512项
    #define BlockSize MemorySize/BlockNum    //每个存储块大小
    #define StartAddr 0         //外部存储块的访问首地址(通过EXMC接口的话可查阅手册)
    
    int MemTable[100] = {0};    //下标为单数的元素为申请地址的首项,单数+1块为末项
    int MemNum = 0;             //已被分配的存储块数量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:每块存储块的大小决定了能申请的地址最小值。

    内存分配原理

           内存分配需要实现1个API函数,该函数的传入参数有两个:接收指针、申请地址大小。其中,接收指针用于存放分配的地址块首地址,申请地址大小应该以字节为单位。

           在该函数中,需要完成以下事项:

    • 根据申请地址的大小,计算分配存储块的数量。
    • 从下标为0开始,根据MemTable,寻找足够、未被分配、连续的存储块以分配,若实在找不到,则返回0表示分配失败。
    • 表示已被分配的块数量加1。
    • 在MemTable中登记。
    • 计算地址块首地址后,将其赋值给接收指针。

    内存释放原理

           内存释放同样需要1个API函数,该函数仅需要1个传入参数,即接收指针。接收指针用于计算首末地址块的位置。

           在该函数中,需要完成以下事项:

    • 根据接收指针及存储区域首地址,计算首末地址块的位置。
    • 将MemTable中,被释放的内存块对应两个元素后开始,所有元素下标减2。
    • 表示已被分配的块数量减1

    参考代码

    以下程序仅供参考!

    宏定义及.c文件内部变量

    #define MemorySize  512*1024//外部存储块总大小
    #define BlockNum  512       //将外部存储块分为512块,对应内存管理表第1项至第512#define BlockSize MemorySize/BlockNum    //每个存储块大小
    #define StartAddr 0         //外部存储块的访问首地址(通过EXMC接口的话可查阅手册)
    
    int MemTable[100] = {0};    //下标为单数的元素为申请地址的首项,单数+1块为末项
    int MemNum = 0;             //已被分配的存储条数量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    内存分配

    int MemDistribute(int size, char* p)
    {
      int num;     //分配存储块的数量
      int i = 0;   //已确定块的数量
      int j = 0;
      int Startaddr = 1;
    
      num = size / BlockSize;
      if((size % BlockSize) != 0)num++;  //整数存储块放不下
      
      while(i < num)  //通过循环寻找足够、未被分配、连续的存储块
      {
        if((Startaddr + size - 1) >= BlockNum)return 0;  //剩余空间不足,返回
        
        for(j = 0; j < MemNum; j++)   //循环检查该块是否已被分配
        {
          if(MemTable[j*2] == (Startaddr+i)) //如果该块已被分配为其它存储块的首地址(只可能是首地址)
          {
            Startaddr = MemTable[j*2+1]+1;    //跳过这一整段被分配的内存
            i = 0;
            break;
          }
        }
        
        if(MemTable[j*2] == 0) //循环了所有被分配的存储条,该块未被分配
        {
          i = i+1;                 //进行下一块的判断
        }
      }
      
      MemNum++;
      MemTable[(MemNum-1)*2] = Startaddr;
      MemTable[(MemNum-1)*2+1] = Startaddr + size -1;
      p = (char*)(StartAddr + Startaddr * BlockNum);
      
      return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    内存释放

    int MemRelease(char* p)
    {
      int num;    
      int i = 0;   
      int j = 0;
      int Startaddr;
    
      Startaddr = (int)(p - StartAddr) / BlockNum;
      
      if((Startaddr > BlockNum) || (MemNum == 0))return 0;
      
      while(i < MemNum)
      {
        if(Startaddr == MemTable[i*2])
        {
          for(j = i; j < MemNum + 1; j++)
          {
            MemTable[j*2] = MemTable[j*2+2];
            MemTable[i*2+3] = MemTable[i*2+3];
          }
          MemNum--;
          break;
        }
        i++;
      }
      
      return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    Redis集群研究和实践(基于redis 3.2.5)(一)
    如何将 ASP.NET Core MVC 项目的视图分离到另一个项目
    python的环境,你再也不用愁-conda
    数组传回后端总显示为空怎么解决
    【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷01
    Python 音频处理工具库之pydub使用详解
    使用PySpark计算AUC,KS与PSI
    ExpressLRS开源代码之工程结构
    独立站如何有效突破独立站转化率瓶颈
    【正点原子STM32连载】第五十八章 USB虚拟串口(Slave)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
  • 原文地址:https://blog.csdn.net/weixin_47447179/article/details/126445067