zoukankan      html  css  js  c++  java
  • 内存管理实验

      一、内存管理的原理

      通过本实验,主要通过介绍程序运行过程中采用到C语言中的那些知识点,其中比较好用的是结构体,比较有趣的是C语言的灵魂“指针”。

      在STM32f103芯片上的内存管理实验,主要是采用分块式内存管理,即把内存(也叫内存池)分成整数块(一般每块内存取32字节或者64字节),同时在内存池中的每一块内存都对应着内存管理表的一块。

      

      分配过程:当要分配n块内存,从内存管理表的顶部往底层寻找,直到找到连续的n块空内存(非0表示该内存块已经被占用),以此时的内存管理表的地址为起点(包括这个地址),往上n块都值成数字“n”,方便以后删除释放内存,同时返回一个地址指针(表示内存管理表分配完内存之后所在)。看

    mymalloc(memx,size)。

      释放过程:当要释放内存时,要用到myfree(memx,fp),其中(u32)fp-(u32)malloc_dev.membase[memx]=offset(偏移量),根据偏移量可得出index=offset/memblksize[memx],即内存管理表所在的块,malloc_dev.memmapbase[memx][index]等于需要释放的块数n,以index块为起点,把接下来的n块内存管理表清零。

      参数定义:

      既然这里涉及到许多内存参数,学过C语言的同学知道可以用到结构体,其封装性可以大大提高我们对所对应的参数的属性的了解。

    struct __Malloc_dev                           //此结构体定义在malloc.h中,是对各种参数的定义
    {
        void (*init)(u8);                        //函数指针(*init), 此函数带有u8 类型的形参,返回(void ) 
        u8 (*perused)(u8);                       //函数指针(*perused),此函数带有u8类型的形参,返回(u8)
        u8     *membase[SRAMBANK];              //一个二维数组(*membase[SRAMBANK]),此数组为u8 类型--->内存池
        u16  *memmapbase[SRAMBANK];           //一个二维数组(*memmapbase[SRAMBANK]),此数组为u16 类型----〉内存管理表
        u8   memready[SRAMBANK];                //一个数组(memready[SRAMBANK]),此数组为u8 类型            ----〉内存状态
    };

    在malloc.c文件中,对结构体初始化

    struct __Malloc_dev  malloc_dev=
    {
        mymalloc_init,                    //(*init)==mymalloc_init  函数名mymalloc_init 本质上也是一个地址指针(这个指针指向一个函数名所代表的函数)
        mymalloc_perused,                //(*perused)==mymalloc_perused  函mymalloc_perused 本质上也是一个地址指针(这个指针指向一个函数名所代表的函数)
        membase1,membase2,            //内存池的个数
        memmapbase1,memmapbase2,//内存管理表的个数
        0,0                                            //每个内存的装备状态(0代表还未装备好,1代表准备好了)
    };

    各个初始化的变量都要有定义,不然编译器会报错!!!

    //定义内存池和内存管理表的数组
    __align(32) u8 membase1[MEM1_MAX_SIZE];                                                                    //32位对齐,定义内存池1的数组
    __align(32) u8 membase2[MEM2_MAX_SIZE]  __attribute__((at(0x68000000)));//32位对齐,定义内存池2的数组,因为起始地址跟SRAM的地址不一致,
                                               //故要重新定位
    u16 memmapbase1[MEM1_TABLE_SIZE]; //定义内存管理表1 u16 memmapbase2[MEM2_TABLE_SIZE] __attribute__((at(0x68000000+MEM2_MAX_SIZE)));//定义内存管理表2

    //设置内存
    //*s:内存首地址
    //c :要设置的值
    //count:需要设置的内存大小(字节为单位)
    void mymemset(void *s,u8 c,u32 count)
    {
    u8 *xs = s;
    while(count--)*xs++=c;
    }

    void mymalloc_init(u8 memx)
    {
        mymemset(malloc_dev.membase[memx],0,memmaxsize[memx]);                  //通过memset函数对内存池的所有块清零
        mymemset(malloc_dev.memmapbase[memx],0,memtablesize[memx]*2);          //通过memset函数对内存管理表的所有块清零
        malloc_dev.memready[memx]=1;                                            //把内存状态置一表示已经初始化了
    }
    
    u8 mymalloc_perused(u8 memx)
    {
        u32 i;
        u32 used=0;
        for(i=0;i<memtablesize[memx];i++)                                     //遍历整个内存的内存管理表
        {
            if(malloc_dev.memmapbase[memx][i]!=NULL) used++;                  //找到内存管理表所有非零的项,并统计出总和
        }
        return used*100/memtablesize[memx];                                   //把所有非零项总和/整个内存管理表*100,表示内存管理表的使用率
        
    }

      从上面的两个指针函数的应用,就可知指针的威力,

       void (*init)(u8);                        //函数指针(*init), 此函数带有u8 类型的形参,返回(void ) 
        u8 (*perused)(u8);               //函数指针(*perused),此函数带有u8类型的形参,返回(u8)
    通过指针(此指针指向函数)就可对一个函数完成定义,在此工程中,由于初始化结构体之后,
    void (*init)(u8)   相当于    void mymalloc_init(u8 memx)
    u8 (*perused)(u8)  相当于  u8 mymalloc_perused(u8 memx)



    除了以上的结构体定义之外,还需要对内存块大小、内存池大小、内存管理表的数量进行定义
    #ifndef NULL
    #define NULL 0
    #endif
    
    
    //内存的块数
    #define SRAMIN  0      //内部内存池
    #define SRAMEX  1      //外部内存池
    #define SRAMBANK 2      //内存池的数量
    
    //内存1的具体参数
    #define     MEM1_BLK_SIZE     32    //内存块大小
    #define MEM1_MAX_SIZE 40*1024       //内存池大小

    #define MEM1_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLK_SIZE   //内存管理表的数量

    //内存2的具体参数

    #define MEM2_BLK_SIZE 32         //内存块大小

    #define MEM2_MAX_SIZE 32*1       //内存池大小

    #define MEM2_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLK_SIZE    内存管理表的数量

    为了方便调用,把这些分类地定义数组

    const u32 memblksize[SRAMBANK]={MEM1_BLK_SIZE,MEM2_BLK_SIZE};    //内存块大小
    const u32 memmaxsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE};     //内存池的大小
    const u32 memtablesize[SRAMBANK]={MEM1_TABLE_SIZE,MEM2_TABLE_SIZE};  //内存管理表的数量



    二、内存的申请和释放详解
    内存申请:
    //内存分配(内部调用)
    //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;//未找到符合分配条件的内存块  
    }  
    
    
    //分配内存(外部调用)
    //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:所属内存块
    //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;  
            }  
            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);    //释放内存      
    } 
    三、main函数中的调用

    int main(void)
     {     
        u8 key;         
         u8 i=0;        
        u8 *p=0;
        u8 *tp=0;
        u8 paddr[18];                //存放P Addr:+p地址的ASCII值
      
        delay_init();             //延时函数初始化      
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
        uart_init(115200);         //串口初始化为115200
         LED_Init();                      //初始化与LED连接的硬件接口
        KEY_Init();                    //初始化按键
        LCD_Init();                       //初始化LCD   
         my_mem_init(SRAMIN);        //初始化内部内存池 
        POINT_COLOR=RED;            //设置字体为红色 
        LCD_ShowString(30,50,200,16,16,"ELITE STM32F103 ^_^");    
        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,"2015/1/20"); 
        LCD_ShowString(30,130,200,16,16,"KEY0:Malloc  KEY1:Free");
        LCD_ShowString(30,150,200,16,16,"KEY_UP:Write");
    
         POINT_COLOR=BLUE;//设置字体为蓝色 
        LCD_ShowString(30,170,200,16,16,"SRAMIN");
        LCD_ShowString(30,190,200,16,16,"SRAMIN USED:   %");  
          while(1)
        {    
            key=KEY_Scan(0);            //不支持连按    
            switch(key)
            {
                case 0:                    //没有按键按下    
                    break;
                case KEY0_PRES:            //KEY0按下
                    p=mymalloc(SRAMIN,2048);//申请2K字节
                    if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容
                    break;
                case KEY1_PRES:            //KEY1按下
                    myfree(SRAMIN,p);    //释放内存
                    p=0;                //指向空地址      
                    break;
                case WKUP_PRES:            //KEY UP按下 
                    if(p!=NULL)
                    {
                        sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容      
                        LCD_ShowString(30,250,200,16,16,p);             //显示P的内容
                    } 
                    break; 
            }
            if(tp!=p)
            {
                tp=p;
                sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);
                LCD_ShowString(30,230,200,16,16,paddr);    //显示p的地址
                if(p)LCD_ShowString(30,250,200,16,16,p);//显示P的内容
                else LCD_Fill(30,250,239,266,WHITE);    //p=0,清除显示
            }
            delay_ms(10);   
            i++;
            if((i%20)==0)//DS0闪烁.
            { 
                LCD_ShowNum(30+96,190,my_mem_perused(SRAMIN),3,16);//显示内部内存使用率
                 LED0=!LED0;
             }
        }       
    }

    其中值得注意的是sprintf(装字符串的数组,“字符串”,写入字符串的内容);

    过程:

    以sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp)为例子,

    tp是一个指针地址,赋给"P Addr:0X%08X"这个字符串,最后赋给paddr这个数组。

    sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容 
    这个也是一个道理,i=>"Memory Malloc Test%03d"=>p(数组)


    总结:要活用这个sprintf函数,可以快速地把一个带参数字符串赋给一个数组变量。
  • 相关阅读:
    写给自己的2020年总结
    docker镜像与docker容器
    docker安装&docker简介
    windows 安装linux子系统
    typora设置图床
    让Mysql插入中文
    pip 换源
    Unity中如何将一个场景(Scene)的Light Settings复制给另一个场景
    Windows API开发
    【C#】判断字符串中是否包含指定字符串,contains与indexOf方法效率问题
  • 原文地址:https://www.cnblogs.com/18689400042qaz/p/13360033.html
Copyright © 2011-2022 走看看