1. 程式人生 > >基於STM32原子戰艦板記憶體管理原始碼詳解

基於STM32原子戰艦板記憶體管理原始碼詳解

走到今天,已經開始涉及到計算機核心一點的東西了---記憶體管理。通過本實驗的學習,能夠較為深刻體會到“指標是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值決定

該函式的意思是把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個變數的  

             一維陣列,每個元素對應的實際意義是對應的一個記憶體塊,順序是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函式的用法////////////////////////////