<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

        基于STM32原子戰艦板內存管理源碼詳解

        作者:王衍   來源:本站原創   點擊數:  更新時間:2014年03月11日   【字體:

             走到今天,已經開始涉及到計算機核心一點的東西了---內存管理。通過本實驗的學習,能夠較為深刻體會到“指針是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函數的用法////////////////////////////

        關閉窗口

        相關文章

        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮