正点原子的内存管理_正点原子【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,"2014/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 模塊上面。
總結
以上是生活随笔為你收集整理的正点原子的内存管理_正点原子【STM32-F407探索者】第四十二章 内存管理实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机常用软件英文读音,常用软件,sof
- 下一篇: 《量子信息与量子计算简明教程》第三章·量