一、內存管理的原理
通過本實驗,主要通過介紹程序運行過程中采用到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函數,可以快速地把一個帶參數字符串賦給一個數組變量。