走到今天,已經開始涉及到計算機核心一點的東西了---內存管理。經過本實驗的學習,可以較爲深入體會到「指針是c語言的靈魂」這句話的份量。天然對c語言的能力要求就高不少了。c++
最近有點亂,可是有關嵌入式系統的學習未曾怠慢過。本文是基於原子老師的c源碼,本身的學習的心得,只是對源碼做出本身的學習理解,同時也插補了一些涉及到的c語言知識。貼出本文不爲別的,一來希望能有有緣人看到本文,提出指正;二來,爲了那些不眠的夜,安慰一下本身。數組
1, 內存管理簡介
內存管理,是指軟件運行時對計算機內存資源的分配和使用的技術。其最主要的目的是如何高效,快速的分配,而且在適當的時候釋放和回收內存資源。內存管理的實現方法有不少種,他們其實最終都是要實現2個函數:malloc和free;malloc函數用於內存申請,free函數用於內存釋放。
先回顧一下c語言知識:計算機內存通常分爲靜態存儲區用以存儲全局變量或常量和動態存儲區用以存儲函數內部變量或形參或函數運算結果。malloc()函數的做用是請求系統在內存的動態存儲區分配若干個字節的存儲空間,函數的返回值是首字節地址,可見malloc()函數是指針類型。free(P)的做用是釋放指針變量P所指向的動態空間。
本章,咱們介紹一種比較簡單的辦法來實現:分塊式內存管理。下面咱們介紹一下該方法的實現原理,如圖所示(示意圖):
內存塊1 內存塊2 內存塊3 ……內存塊n 內存池
| | | |
第1項 第2項 第3項 ……第n項 內存管理表
<<-----分配方向
|
malloc,free等函數
圖解:從上圖能夠看出,分塊式內存管理由內存池和內存管理表兩部分組成。內存池被等分爲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個內存管理表項目的值都清零,標記釋放,完成一次內存釋放。
關於分塊式內存管理的原理,咱們就介紹到這裏。測試
2, 硬件設計:
本章實驗功能簡介:開機後,顯示提示信息,等待外部輸入。KEY0用於申請內存,每次申請2K字節內存。KEY1用於寫數據到申請到的內存裏面。KEY2用於釋放內存。WK_UP用於切換操做內存區(內部內存/外部內存)。DS0用於指示程序運行狀態。本章咱們還能夠經過USMART調試,測試內存管理函數。
本實驗用到的硬件資源有:
1) 指示燈DS0
2) 四個按鍵
3) 串口 //USMART
4) TFTLCD模塊
5) IS62WV51216字體
3, 軟件設計:
本章,咱們將內存管理部分單獨作一個分組,在工程目錄下新建一個MALLOC的文件夾,而後新建malloc.c和malloc.h兩個文件,將他們保存在MALLOC文件夾下。
在MDK新建一個MALLOC的組,而後將malloc.c文件加入到該組,並將MALLOC文件夾添加到頭文件包含路徑。
打開malloc.c文件,輸入以下代碼:因爲本實驗涉及到的c語言知識,尤爲是指針知識較多,因此就邊用邊學
#include "malloc.h"
//內存池(4字節對齊)
__align(4) u8 mem1base[MEM1_MAX_SIZE]; //內部SRAM內存池
/*
" u8 mem1base[MEM1_MAX_SIZE];"該數組是定義拿出內部內存池的40K的空間來作實驗,爲何該數組是u8類型?計算機內存是以字節爲單位的存儲空間,內存中的每一個字節都有惟一的編號,這個編號就叫地址。在這裏就是定義40K個元素,每一個元素表明一個字節。整個數組就表明整個內部SRAM內存池的總容量即40K個元字節的總空間容量。由於不論是存儲什麼數據類型內存中的地址編號都是32位的,即每一個地址編號能夠容納4個字節,而不一樣的數據類型存儲在不一樣的內存存儲區,這就是爲何定義變量時必定要先聲明其數據類型的緣由。存儲一個字符須要一個字節的存儲空間,存儲一個short類型須要2個字節的存儲空間,存儲一個int或float須要4個字節空間,就如同PLC內存中的字節,字,雙字的定義規則同樣(如字節MB0,MB1,MB0和MB1構成MW0;MW0和MW2構成32位的雙字DW0,DW4,DW8)。「__align(4)」就是規定4個字節對齊,即每一個32的地址編號存儲一個數據類型?好比,字符存儲區中地址編號MB0能夠存儲一個字節即8個位的數據,而存儲MB0這個地址編號是以32位的空間來存儲,也就是說不論是什麼類型數據,存儲它的地址編號都是32的,因此指針值必定是32位的。
//「#define MEM1_MAX_SIZE 40*1024 //最大管理內存 40K」,意思是mem1base[MEM1_MAX_SIZE]有40k個元素
*/
__align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));//外部SRAM內存池
//#define MEM2_MAX_SIZE 200*1024 //最大管理內存200K,意思是mem2base[MEM2_MAX_SIZE]數組有200K個u8類型元素,第一個元素的地址存儲在 //外部存儲器SRAM的0X68000000地址,
//內存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //內部SRAM內存池MAP
/*
//#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //內存表大小,MEM1_MAX_SIZE/MEM1_BLOCK_SIZE==1250
//#define MEM1_BLOCK_SIZE 32 //內存塊大小爲32字節;「MEM1_MAX_SIZE/MEM1_BLOCK_SIZE 」的含義是內部SRAM內存池總共40K字節的容量除以32個字節,獲得一共40K/32==1250個內存塊;也就是說將內部SRAM內存池劃爲1250個內存塊。
「u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];」實際上就等於「u16 mem1mapbase[1250];」意思是定義一個有1250個內存塊(元素)的數組,每一個元素是u16類型數據;數組名「mem1mapbase」就是mem1mapbase[0](該數組的第一個元素它表明1250個內存塊中的第一個內存塊)的地址,也能夠說是指針常量;結合與之關聯的結構體成員「u16 *memmap[2]={ mem1mapbase,mem2mapbase}」指針類型數組;在這裏「mem2mapbase」是外部內存的第一個內存塊的地址,是個指針常量用以存放u16類型數據的地址值;結合
「mymemset(mallco_dev.memmap[0], 0,memtblsize[0]*2);」函數分析:結合本程序和結構體有關定義「u16 *memmap[2]; 」,首元素memmap[0]=mem1mapbase;也就是說「mallco_dev.memmap[0]」在這裏表示1250個內部內存塊中第一個內存塊的地址,根據「u16 *memmap[2]={ mem1mapbase,mem2mapbase}」推斷出「mallco_dev.memmap[0]」是u16類型指針;
「memtblsize[0]」是什麼意思呢?根據「const u32 memtblsize[2]={1250,6250};」能夠得知memtblsize[0]==1250即內部內存一共有1250個管理項,
void mymemset(void *s,u8 c,u32 count)
{
u8 *xs = s;
while(count--)*xs++=c;
} //把u8類型數據c填充到以指針變量s爲首地址的內存空間中,填充多少個數由count值決定.net
該函數的意思是把u8類型的數據「c」填充到u16類型指針元素memmap[0]中(根據結構體定義「u16 *memmap[2]; 」,而memmap[0]=mem1mapbase),說白了就是把u8類型的數據「c」填充到1250個內存塊中的count個內存塊中。
而mallco_dev.memmap[memx]是16位的,爲了將其所有清零,因此乘以2.
本例中,用到了指針類型數組「u16 *memmap[2]={ mem1mapbase,mem2mapbase}」,爲何要定義指針類型數組呢?mem1mapbase是數組
「u16 mem1mapbase[1250];」的首個元素地址(即*mem1mapbase等價於mem1mapbase[0]),而mem1mapbase[0]就表明內部存儲器1250個存儲塊中的第一個存儲塊;根據結構體賦值定義可知:memmap[0]=mem1mapbase。因此mem1mapbase就是「mallco_dev.memmap[0]」,即mem1mapbase是函數mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2)的第一個形參,由於*mem1mapbase等價於mem1mapbase[0]),而mem1mapbase[0]就表明內部存儲器1250個存儲塊中的第一個存儲塊。結合
void mymemset(void *s,u8 c,u32 count)函數分析, mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2)函數的意思是:把0寫入到1250個存儲塊中的第一個存儲塊中;這樣就將一個存儲塊的值賦值爲0了。
推斷出「mallco_dev.memmap[0]」是u16類型指針;
;
*/
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE)));
/*
「#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE」
「#define MEM2_BLOCK_SIZE 32」
外部SRAM內存池MAP,同理,「MEM2_MAX_SIZE/MEM2_BLOCK_SIZE」的含義是外部SRAM內存池總共200K字節的容量除以32個字節,獲得一共200K/32==6250個內存塊;也就是說將外部SRAM內存池劃爲6250個內存塊。
*/
//內存管理參數
/*
內存管理表「MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE」分別是1250和6250個「項」.
每一個內存分塊大小即內部和外部SRAM每一個內存塊佔有32個字節空間「MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE」分別是32個字節;
內存總大小「MEM1_MAX_SIZE,MEM2_MAX_SIZE」,分別是40K和200K個字節的總容量空間
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);
*/
const u32 memtblsize[2]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};//內存管理表大小
const u32 memblksize[2]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //內存分塊大小
const u32 memsize[2]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //內存總大小
/*
struct _m_mallco_dev //內存管理控制器,定義一個結構體類型數據,或結構體變量,
{
void (*init)(u8); //初始化
u8 (*perused)(u8); //內存使用率
u8 *membase[2]; //內存池 管理2個區域的內存 mem1base,mem2base內存池
u16 *memmap[2]; //內存管理狀態表 mem1mapbase(==1250塊),mem2mapbase(6250), //內存管理狀態表
u8 memrdy[2]; //內存管理是否就緒
};
1,結構體成員「void (*init)(u8);」是定義了一個指向函數的指針變量,該指針變量名是init;void表示該函數沒有返回值(函數的數據類型由返回值決定);u8是函數的形參。指向函數的指針變量格式:數據類型 + (*變量名)(形參)
本例中:
void mem_init(u8 memx)
{
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//內存狀態表數據清零 memx:所屬內存塊,即幾號內存塊
mymemset(mallco_dev.membase[memx], 0,memsize[memx]); //內存池全部數據清零
mallco_dev.memrdy[memx]=1;//內存管理初始化OK
}
也就是說,本例中用指向函數的指針變量來表示函數。c語言規定函數名就是函數的入口地址,也就是說函數名也是一個指針,指向函數的入口,根據這個原理,能夠將指向函數的指針做爲函數的參數調用,能夠在不一樣的狀況調用不一樣的函數;若是一個指向函數的指針變量等於函數名就能夠說該指向函數的指針變量指向了該函數,那麼指針變量與函數就是同樣的了。好比:「mem_init(memx);」就等同於「mallco_dev.init(memx);」
2,指針類型數組「u8 *membase[2];」,意思是該指針類型數組有兩個「char *」類型的指針元素或者說有兩個「u8 *」類型指針元素;爲何要定義「u8 *」類型呢?由於內存存儲區是根據數據類型來劃分的,若是不明確聲明類型就亂套了。
在C語言和C++語言中,數組元素全爲指針的數組稱爲指針數組。一維指針數組的定義形式爲:「類型名 *數組標識符[數組長度]」。
例如,一個一維指針數組的定義:int *ptr_array[10]。該指針數組有10個元素,每一個元素都是int類型的指針即「int *」類型;
指針類型數組「u8 *membase[2];」的賦值是mem1base,mem2base, 「mem1base,mem2base」分別是內部內存池和外部內存池的數組名,是指針常量即首元素的地址;由於事先已經定義「u8 mem1base[MEM1_MAX_SIZE]」即「u8 mem1base[40K];」。
*/
//內存管理控制器,結構體變量賦值,即初始化
struct _m_mallco_dev mallco_dev=
{
mem_init, //內存初始化,將函數名「mem_init」賦給結構體成員「void (*init)(u8);」即指向函數的指針變量,
mem_perused, //內存使用率
mem1base,mem2base, //內存池
mem1mapbase,mem2mapbase, //內存管理狀態表,mem1mapbase(1250項),mem2mapbase(6250項)
0,0, //內存管理未就緒
};
/*
1,「void *des」無類型指針,不能指向具體的數據,「void *des」無類型指針指向內存中的數據類型由用戶本身肯定,如malloc()函數的返回值就是「void *des」無類型指針,由於malloc()函數的返回值是不肯定的是根據形參的數據類型肯定的
2,「void mymemcpy(void *des,void *src,u32 n) 」函數的理解:
des是指針,可是不肯定指向什麼類型的數據,換句話說des指針存儲的什麼類型數據不肯定,「u8 *xdes=des;」將des指針存儲的數據
存儲到一個新的「u8 *」類型指針xdes中;「u8 *xsrc=src;」同理。
「*xdes++=*xsrc++; 」,當*xsrc++(自增)時,即指針「src」指針自增,也就是說把「src」指針逐位複製到des目標指針去了。複製個數就是n。
3,「*P」的意義:a),「*P」就是以指針變量P的內容(P的內容就是指針變量P裏存儲的某一類型數據的指針值)爲地址的變量;b),指針運算符「*」若是是在定義變量時候加在前面,意思是這個變量是指針變量,如 char *a;若是是在訪問指針變量的時候加在前面(如*a),意思是取指針變量指向的值,如 char b=*a; 上面定義了a是一個字符指針,這裏就是把指針變量a指向的值取出來並賦給b。
*/
//複製內存,做用是將源地址的內容複製到目標地址
//*des:目的地址
//*src:源地址
//n:須要複製的內存長度(字節爲單位)
void mymemcpy(void *des,void *src,u32 n)
{ //「void *des」無類型指針,不能指向具體的數據,「void *des」無類型指針指向內存中的數據類型由用戶本身肯定
u8 *xdes=des;//目標地址,「*xdes」轉換成u8類型,也能夠理解爲把目的地地址des存儲到xdes指針中
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;
} //以*s爲內存首地址的count個字節中,填充c,即把c寫入到*s爲首地址的內存中,個數多少由count值決定
//內存管理初始化
//memx:所屬內存塊,要麼SRAMEX==1(外部內存);要麼SRAMIN(內部內存)==0
/*
const u32 memtblsize[2]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};//內存管理表大小
const u32 memblksize[2]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //內存分塊大小
const u32 memsize[2]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //內存總大小設計
*/
void mem_init(u8 memx) //如「mem_init(SRAMIN);」表示內部內存塊
{ //memmap,是16位的,mymemset,設置是針對8位的,那麼1個16位的數據是否是2個8位組成的啊?!
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//內存狀態表數據清零
//把u8類型的數據「0」填充到u16類型指針元素memmap[0]中(根據結構體定義「u16 *memmap[2]; 」),memmap[0]=mem1mapbase==1250,
//也就是說「mallco_dev.memmap[memx]」在這裏表示1250個內部內存塊用以存儲u16類型指針,
//「memtblsize[memx]」是什麼呢?memtblsize[memx]即memtblsize[0]==1250個內部內存管理表,
//而mallco_dev.memmap[memx]是16位的,爲了將其所有清零,因此乘以2.
mymemset(mallco_dev.membase[memx], 0,memsize[memx]); //內存池全部數據清零
//memsize[0]==40K字節空間, mallco_dev.membase[memx]==40K字節空間,
mallco_dev.memrdy[memx]=1; //內存管理初始化OK
}
/*
*/
//獲取內存使用率
//memx:所屬內存塊,要麼SRAMEX==1(外部內存);要麼SRAMIN(內部內存)==0
//返回值:使用率(0~100)
u8 mem_perused(u8 memx)
{
u32 used=0;
u32 i;
for(i=0;i<memtblsize[memx];i++)
{
if(mallco_dev.memmap[memx][i])used++;
} //mallco_dev.memmap[memx][i]是二維數組。當內存塊初始化後該值爲0,
return (used*100)/(memtblsize[memx]); //used*100,乘以100是將小數變成整數
}
//內存分配(內部調用)
//memx:所屬內存塊
//size:要分配的內存大小(字節數)
//返回值:0XFFFFFFFF,表明錯誤;其餘,內存偏移地址
//向memx存儲器申請size個字節的連續存儲空間,並將size個字節中首個字節的地址偏移值標註出來,注意是地址偏移值而不是地址。
u32 mem_malloc(u8 memx,u32 size)
{
signed long offset=0;
u16 nmemb; //須要的內存塊數
u16 cmemb=0;//連續空內存塊數
u32 i;
if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先執行初始化
/*
「mallco_dev.init(memx);」是什麼意思?mallco_dev.init(memx)是結構體變量mallco_dev的一個成員,本句中就是對結構體成員的引用,即執行
mem_init(u8 memx)函數的意思;如何引用結構體中指向函數的指針變量成員?既然是指向函數的指針變量且有賦值,在引用時按照格式:
結構體變量名.指向函數的指針變量名(形參);
*/
if(size==0)return 0XFFFFFFFF;//不須要分配 memblksize[memx]==32
nmemb=size/memblksize[memx]; //獲取須要分配的連續內存塊數
/*
c語言規定:除法的運算結果與運算對象的數據類型有關,兩個數都是int則商(即結果)是int,若商(即結果)有小數則省略掉小數點部分。本例中
size和memblksize[memx]都是int,因此結果只能是int。假設size<32,則nmemb==0;
c語言規定取餘運算的運算對象必須是int。當小數對大數取餘時餘(即結果)是小數自己;例如,在「if(size%memblksize[memx])nmemb++;」中 ,
假設size<32,則size%memblksize[memx]的結果是size值自己,因此執行「nmemb++;」運算,這時運算結果是nmemb==1;若是size是32的整數倍則不執行
「nmemb++;」運算;
memtblsize[0]==1250,memtblsize[1]==6250,
mallco_dev.memmap[memx][offset]是什麼意思?
*/
if(size%memblksize[memx])nmemb++;
for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整個內存控制區
{
if(!mallco_dev.memmap[memx][offset])cmemb++;//連續空內存塊數增長,offset從1249->0變化
/*
如,{ memmap[0][149],memmap[0][148],...memmap[0][1],memmap[0][0]};實際上能夠把「mallco_dev.memmap[memx][offset]」視爲具備1250個變量的 3d
一維數組,每一個元素對應的實際意義是對應的一個內存塊,順序是offset從1249(高)->0(低)變化;若是哪一個變量等於0(即空閒)就執行
「cmemb++;」操做,這樣就能夠計算出連續空閒內存塊數cmemb;切記!目的是要獲取連續的空閒的內存塊數!這樣就必須結合下一句
「else cmemb=0;」來分析;若是沒有出現連續的空閒內存塊(即數組順序相連的變量值沒有出現相似「0,0,0,0,0」這樣的狀況),程序會執行下一語
句「else cmemb=0;」即把上面的「cmemb」統計值清零,這樣程序就會在for循環裏面繼續尋找符合「if(cmemb==nmemb)」條件的狀態出現,
若是for循環執行完了尚未出現符合「if(cmemb==nmemb)」條件的狀態,則返回0XFFFFFFFF結束本函數表示沒有找到符合條件的內存塊。假
設:size=65,那麼nmemb就是3即須要獲取連續3個內存塊來存放65個字節,再假設數組順序相連的變量值出現了相似「0,0,0,0,0」這樣的狀況(即有
連續4個空閒的內存塊),這時就出現了符合「if(cmemb==nmemb)」條件的狀態,即當cmemb計數計到3的時候(即出現了連續相連的3個內存塊)就
符合「cmemb==nmemb」了,程序就天然進入「if(cmemb==nmemb)」語句。
offset*memblksize[memx]表明什麼呢?offset的取值範圍是0-1249,memblksize[memx]表明每一個內存塊的字節數即32,offset*memblksize[memx]就
是返回偏移地址值;也就是把連續空閒的內存塊對應的地址的首地址值標註出來。
*/
else cmemb=0; //連續內存塊清零
if(cmemb==nmemb) //找到了連續nmemb個空內存塊
{
for(i=0;i<nmemb;i++) //標註內存塊非空,以避免下一個for循環時再次將該空間計入
{
mallco_dev.memmap[memx][offset+i]=nmemb;
}
return (offset*memblksize[memx]);//返回偏移地址
}
}
return 0XFFFFFFFF;//未找到符合分配條件的內存塊
}
//釋放內存(內部調用)
//memx:所屬內存塊
//offset:內存地址偏移
//返回值:0,釋放成功;1,釋放失敗;
u8 mem_free(u8 memx,u32 offset)
{
int i;
if(!mallco_dev.memrdy[memx])//未初始化,先執行初始化
{
mallco_dev.init(memx); //本句等價於「mem_init(memx);」
return 1;//未初始化
}
if(offset<memsize[memx])//以避免偏移在內存池內. memsize[memx]==40K字節
{
int index=offset/memblksize[memx]; //偏移所在內存塊號碼 memblksize[memx]==32,
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];
mem_free(memx,offset);//釋放內存
}
//分配內存(外部調用)
//memx:所屬內存塊
//size:內存大小(字節)
//返回值:分配到的內存首地址.
//在memx存儲器中,找出size個字節的連續空閒的內存空間,並將連續空閒的內存空間指針值標註出來;返回值就是這個指針值
/*
mallco_dev.membase[memx]即mallco_dev.membase[0]表明MCU內部存儲器的40K字節中的第一個字節變量的地址,是u8類型指針變量,也就是說一個字節佔用一個地址;換句話說,把內部存儲器的40K字節的地址定義爲一個「u8 mem1base[MEM1_MAX_SIZE]」數組,指針類型數組「u8 *membase[2];」的賦值是{mem1base,mem2base},而「mem1base,mem2base」分別是內部內存池和外部內存池的數組名,各自首元素的地址亦是個指針常量;由於事先已經定義
「u8 mem1base[MEM1_MAX_SIZE]」即「u8 mem1base[40K];」。如何理解「(void*)((u32)mallco_dev.membase[memx]+offset); 」呢?
1),已經說過mallco_dev.membase[memx]是首個變量的地址即40k字節中首個字節的地址值;
2),「offset」是:向memx存儲器申請size個字節的連續空閒存儲空間,這個找到的連續空閒空間當中首個字節的地址偏移值就是offset,offset==32(將32個字節空間組成一個內存塊)*內存塊號(如,假設向內部存儲器申請64個字節的連續空閒存儲空間,經過「mem_malloc(memx,size); 」函數獲得在第五個存儲塊開始有連續2個存儲快空閒可供使用(假設是5號和4號存儲快),由於每一個存儲快有32個字節即有32個地址編號,4*32==128(這裏的4是指第四塊),5*32==160(這裏的5是指第五塊),那麼這個160就是40K個字節編號當中的地址偏移值offset,即128-192號就是第四塊和第五塊內存塊所對應的指針編號);注意offset是地址偏移值而不是地址;爲何要引入地址偏移值這個概念呢?假設第一個字節的地址值是0x0000 6800,那麼就知道(0x0000 6800+160)的值就是第五塊內存的指針。
3),「(u32)mallco_dev.membase[memx]」表明指針類型數組,意義是內部存儲器40K字節中的第一個字節變量的地址,原來存放的是u8類型數據的地址,如今強制類型轉換擴展爲u32類型;
4),(void*)((u32)mallco_dev.membase[memx]+offset); 轉換爲無類型指針,指針值是32位,由此可知,「void *mymalloc(u8 memx,u32 size)」函數的返回值就是一個指針,即形參size所指向的由高向低的首個指針值;「void *mymalloc(u8 memx,u32 size)」是個指針類型函數,只能賦給指針。
*/
void *mymalloc(u8 memx,u32 size) //p=mymalloc(sramx,2048)
{
u32 offset;
offset=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=mem_malloc(memx,size);
if(offset==0XFFFFFFFF)return NULL;
else
{
mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size); //拷貝舊內存內容到新內存
// 把size個字節指針ptr複製到「((u32)mallco_dev.membase[memx]+offset)」,
myfree(memx,ptr); //釋放舊內存,由於在mem_malloc(memx,size)中已經將連續空閒內存塊標註爲1(已被佔用),清除掉原來的標記
return (void*)((u32)mallco_dev.membase[memx]+offset); //返回新內存首地址,無類型指針
}
}指針
頭文件:
#ifndef __MALLOC_H
#define __MALLOC_H
typedef unsigned long u32;
typedef unsigned short u16;
typedef unsigned char u8;
#ifndef NULL
#define NULL 0
#endif
#define SRAMIN 0 //內部內存池
#define SRAMEX 1 //外部內存池
//mem1內存參數設定.mem1徹底處於內部SRAM裏面
#define MEM1_BLOCK_SIZE 32 //內存塊大小爲32字節
#define MEM1_MAX_SIZE 40*1024 //最大管理內存 40K
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //內存表大小
//mem2內存參數設定.mem2的內存池處於外部SRAM裏面,其餘的處於內部SRAM裏面
#define MEM2_BLOCK_SIZE 32 //內存塊大小爲32字節
#define MEM2_MAX_SIZE 200*1024 //最大管理內存200K
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //內存表大小
struct _m_mallco_dev //內存管理控制器
{
void (*init)(u8); //初始化
u8 (*perused)(u8); //內存使用率
u8 *membase[2]; //內存池 管理2個區域的內存
u16 *memmap[2]; //內存管理狀態表
u8 memrdy[2]; //內存管理是否就緒
};
extern struct _m_mallco_dev mallco_dev; //在mallco.c裏面定義,定義全局變量,結構體變量mallco_dev
void mymemset(void *s,u8 c,u32 count); //設置內存
void mymemcpy(void *des,void *src,u32 n);//複製內存
void mem_init(u8 memx); //內存管理初始化函數(外/內部調用)
u32 mem_malloc(u8 memx,u32 size); //內存分配(內部調用)
u8 mem_free(u8 memx,u32 offset); //內存釋放(內部調用)
u8 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,都是32字節。內存池總大小,內部爲40K,外部爲200K(最大支持到近1M字節,不過爲了方便演示,這裏只管理200K內存)。MEM1_ALLOC_TABLE_SIZE和MEM2_ALLOC_TABLE_SIZE,則分別表明內存池1和2的內存管理表大小。
從這裏能夠看出,若是內存分塊越小,那麼內存管理表就越大,當分塊爲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
Stm32_Clock_Init(9); //系統時鐘設置
uart_init(72,9600); //串口初始化爲9600
delay_init(72); //延時初始化
led_init(); //初始化與LED鏈接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
Key_Init(); //按鍵初始化
FSMC_SRAM_Init(); //初始化外部SRAM,由於用到了外部sram
mem_init(SRAMIN); //初始化內部內存池,SRAMIN==0
mem_init(SRAMEX); //初始化外部內存池,SRAMEX==1
POINT_COLOR=RED;//設置字體爲紅色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"MALLOC TEST");
LCD_ShowString(60,90,200,16,16,"WANG YAN");
LCD_ShowString(60,110,200,16,16,"2013/12/16");
LCD_ShowString(60,130,200,16,16,"key_right:Malloc key_left:Free");
LCD_ShowString(60,150,200,16,16,"wake_up:SRAMx key_down:Read");
POINT_COLOR=BLUE;//設置字體爲藍色
LCD_ShowString(60,170,200,16,16,"SRAMIN");
LCD_ShowString(60,190,200,16,16,"SRAMIN USED: %");
LCD_ShowString(60,210,200,16,16,"SRAMEX USED: %");
while(1)
{
key=Key_Scan(0);//不支持連按
switch(key)
{
case 0://沒有按鍵按下
break;
case key_right: //KEY0按下
p=mymalloc(sramx,2048);//申請2K字節,即64個內存塊的空間
if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p寫入一些內容
break;
case key_down: //KEY1按下
if(p!=NULL) //NULL==0;
{
sprintf((char*)p,"Memory Malloc Test%03d",i);//更新顯示內容
// LCD_ShowString(60,270,200,16,16,p);
LCD_ShowString(60,250,200,16,16,p);//顯示P的內容
printf("Memory Malloc Test%03d\n",i);//將「Memory Malloc Test」用串口輸出,利用串口助手能夠看到輸出的結果
//"03"表示參數「i」的值只顯示3位,%-輸出控制符;d-將「i」以十進制的形式輸出;i的範圍0--255;輸出參數能夠是多個,能夠參考郝斌老師的相關視頻;
//輸出控制符包含:%Ld--L表明long類型;%c--表明字符類型;:%X--表明16進制並大寫;
}
break;
case key_left: //KEY2按下
myfree(sramx,p);//釋放內存
p=0; //指向空地址
break;
case wake_up: //KEY UP按下
sramx=!sramx;//切換當前malloc/free操做對象
if(sramx)LCD_ShowString(60,170,200,16,16,"SRAMEX");
else LCD_ShowString(60,170,200,16,16,"SRAMIN");
break;
}
if(tp!=p)
{//在內存paddr值處顯示:「P Addr:0X%08X」,「0X%08X」以大寫16進制顯示參數tp(32位),「08」表示8位數。0X AAAA AAAA
//剛進入程序時,由於執行了「mem_init(SRAMIN);」初始化函數,因此p==0;因此LCD不會有顯示
//由於程序一開始就有「u8 *tp=0;」,因此若不按下任何按鍵LCD就不會顯示下面的內容(即「if(tp!=p)」控制的顯示內容);
tp=p;//PAddr顯示的是指針p自己的地址值;指針值是u32類型
sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);//將指針p自己的地址值在LCD上打印出來即顯示
LCD_ShowString(60,230,200,16,16,paddr); //顯示p的地址
if(p)
LCD_ShowString(60,250,200,16,16,p);//顯示P的內容,即指針p內存儲的數據「Memory Malloc Test%03d」
else LCD_Fill(60,250,239,266,WHITE); //p=0,清除顯示
}
delay_ms(10);
i++;
if((i%20)==0)//DS0閃爍.
{
LCD_ShowNum(60+96,190,mem_perused(SRAMIN),3,16);//顯示內部內存使用率
LCD_ShowNum(60+96,210,mem_perused(SRAMEX),3,16);//顯示外部內存使用率
led0=!led0;
}
}
}
總結:經過內存管理的學習,更加深入的領會到指針是c語言的靈魂,對c語言的知識是一個鞏固和提升;同時也學習到了sprintf()函數的運用技巧。
本章但願利用USMART調試內存管理,因此在USMART裏面添加了mymalloc和myfree兩個函數,用於測試內存分配和內存釋放。你們能夠經過USMART自行測試。
4,下載驗證:
在代碼編譯成功以後,咱們經過下載代碼到ALIENTEK戰艦STM32開發板上,獲得如圖所示界面:
能夠看到,內外內存的使用率均爲0%,說明尚未任何內存被使用,此時咱們按下KEY0,就能夠看到內部內存被使用5%(每按下一次申請2K的空間,lcd上顯示的使用率遞增5%;20*2K==40K)了,同時看到下面提示了指針p所指向的地址(其實就是被分配到的內存地址)和內容。多按幾回KEY0,能夠看到內存使用率持續上升(注意對比p的值,能夠發現是遞減的,說明是從頂部開始分配內存!),此時若是按下KEY2,能夠發現內存使用率下降了5%,可是再按KEY2將再也不下降,說明「內存泄露」了。這就是前面提到的對一個指針屢次申請內存,而以前申請的內存又沒釋放,致使的「內存泄露」。
按KEY_UP按鍵,能夠切換當前操做內存(內部內存/外部內存),KEY1鍵用於更新p的內容,更新後的內容將從新顯示在LCD模塊上面。
本章,咱們還能夠藉助USMART,測試內存的分配和釋放,有興趣的朋友能夠動手試試。如右圖USMART測試內存管理函數所示。
/////////////////////////插補:printf和sprintf函數的用法////////////////////////////
printf和sprintf函數的用法很是重要,用於程序參數調試。這兩個函數都包含在系統啓動代碼「stdio.h」頭文件中;
1,例:printf("Memory Malloc Test%03d\n",i);//將「Memory Malloc Test」用串口輸出,利用串口助手能夠看到輸出的結果;
"03"表示參數「i」的值只顯示3位,%d-輸出控制符;d-將「i」以十進制的形式輸出;i的範圍0--255(由於是u8類型);輸出參數能夠是多個,能夠參考郝斌老師的相關視頻;輸出控制符包含:%Ld--L表明long類型;%c--表明字符類型;:%X--表明16進制並大寫;%s-字符串類型
2,如何理解字符串打印函數int sprintf(char * __restrict /*s*/, const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));?
在內存管理實驗中例如,sprintf((char*)p,"Memory Malloc Test%03d",i)函數的使用問題:
1),第一個形參(char*)p的意思是(第一個形參必須是指針類型),第二個形參即字符串「Memory Malloc Test%03d」存儲在內存中的具體指針值,由於字符串是u8類型即char*類型,因此「(char*)p」與之呼應;由於第二個形參「Memory Malloc Test%03d」中有輸出控制符「%03d」,因此第一個形參(char*)p的值是變化的(由於參數「i」的值在變);這裏輸出控制符「%03d」的意思能夠參考printf()函數;
也就是說,sprintf函數的第一個形參必須是指針類型,它是第二個形參(輸出內容)在存儲器中存儲的首地址,是一個指針變量,第三個形參就是要輸出的參數;因此說sprintf函數包含的內容不少,做用很大。
2),sprintf函數的做用是在顯示屏中顯示相關參數,即向p寫入一些內容即Memory Malloc Test%03d」;
結合LCD_ShowString(60,270,200,16,16,p)的顯示結果更好理解,即顯示P的存儲內容即在相應的座標處「Memory Malloc Test%03d」;」
3),例子:
u8 s[8];
char* who = "I"; //將字符「I」賦給char* 類型變量who;
char* whom = "STM32"; //將字符串「STM32」賦給char* 類型變量whom;
sprintf(s, "%s love %s.", who, whom); //產生:"I love STM32. " 這字符串寫到s中
LCD_ShowString(60,250,200,16,16,s);
//sprintf(s, "%10.3f", 3.1415626); //產生:" 3.142",浮點型顯示
4),sprintf函數通常狀況下是用在須要字符顯示的場合,好比你要顯示一個數字,一般的作法是取出某一位而後加上0x30這個數,這樣一位一位來比較麻煩,用sprintf這個函數呢,一次性就給你搞定了
好比你想打印3.1415926這個數值到液晶上顯示,一般的作法代碼就不少並且亂,有了這個函數呢,直接這樣
float PI=3.1415926;
u16 strbuffer[10];
sprintf(strbuffer,"PI=:%09d",PI);
而後直接將strbuffer這個數組送去顯示便可,或者打印到串口,這樣就能夠直接字符顯示了
注意:sprintf函數必須結合LCD顯示函數使用纔能有效!而且形參必須定義好合適的數據類型;sprintf()函數的最大做用就是很是方便的在LCD顯示屏上顯示本身想要的數據類型!參考關於sprintf函數的實驗。
3,疑問?
a,在51單片機中,如何將sprintf函數包含進51的啓動代碼中?若是不將sprintf函數包含進51的頭文件,顯示屏確定不能用sprintf函數顯示數據。
b,在stdio.h中,找到的是int sprintf(char * __restrict /*s*/, const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));怎麼看不到函數內容?
sprintf是C語言標準庫提供的函數, 包含在stdio.h中, 只要在文件頭#include <stdio.h>便可.
原型爲int sprintf ( char * str, const char * format, ... );
/////////////////////////插補:printf和sprintf函數的用法////////////////////////////
https://blog.csdn.net/doudoududu1314/article/details/78186661