參考原文《STM32F1開發指南》
內存管理簡介
內存管理,是指軟件運行時對計算機內存資源的分配和使用的技術。最主要的目的是如何高效、快速的分配,並且在適當的時候釋放和回收內存資源。內存管理的實現方法有很多種,但最終是要實現2個函數:malloc(內存申請)和free(內存釋放)。
STM32 原子開發板採用的實現方法是:分塊式內存管理。
分塊式內存管理
分塊式內存管理由內存池和內存管理表兩部分組成。內存池和對應的內存表都分成n塊,是相互對應的。
內存管理表的項值代表的意義為:當該項值為0的時候,代表對應的內存塊未被佔用。當該項值非零的時候,代表 該項對應的內存塊已經被佔用,其數值代表連續佔用的內存塊數。比如某項值為10,説明包含本項在內的內存塊總共佔用了10個分配給外部的某個指針。
內存分配方向如圖所示,是從頂-->底的方向分配的。首先從頂端開始找空內存;當內存管理初始化的時候,內存表全部清零,表示沒有任何內存塊被佔用。
分配原理
當指針 p 調用 malloc 申請內存的時候,先判斷 p 要分配的內存塊數m,然後從第 n 項開始,向下查找,直到找到m塊連續的空內存塊(即對應內存管理表項為0),然後將這 m 個內存管理表項的值都設置為 m(標記被佔用),最後把這個空內存塊的地址返回給指針p,完成一次分配。注意,當內存不夠的時候,則返回 NULL 給 p,表示分配失敗。
釋放原理
當申請的內存用完,需要釋放的時候,調用free函數實現。 free 函數先判斷 p 指向的內存地址所對應的內存塊,然後找到對應的內存管理表項目,得到 p 所佔用的內存塊數目m(內存管理表項目的值就是所分配內存塊的數目),將這m個內存管理表項目的值都清零,標記釋放完成。
源碼
//內存池(32字節對齊)
__align(32) u8 mem1base[MEM1_MAX_SIZE]; //內部SRAM內存池
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000))); //外部SRAM內存池
//內存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //內部SRAM內存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE))); //外部SRAM內存池MAP
//內存管理參數
const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE}; //內存表大小
const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //內存分塊大小
const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //內存總大小
//內存管理控制器
struct _m_mallco_dev mallco_dev=
{
my_mem_init, //內存初始化
my_mem_perused, //內存使用率
mem1base,mem2base, //內存池
mem1mapbase,mem2mapbase, //內存管理狀態表
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<memtblsize[memx];i++)
{
if(mallco_dev.memmap[memx][i])used++;
}
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<nmemb;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<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); //釋放內存
}
//分配內存(外部調用)
//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); //返回新內存首地址
}
}
通過內存管理控制器 malloc_dev 結構體實現對兩個內存池的管理控制。
//內存管理控制器
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裏面定義
//內存管理控制器
struct _m_mallco_dev mallco_dev=
{
my_mem_init, //內存初始化
my_mem_perused, //內存使用率
mem1base,mem2base, //內存池
mem1mapbase,mem2mapbase, //內存管理狀態表
0,0, //內存管理未就緒
};
以下是內存池和內存管理表的定義:
//定義兩個內存池
#define SRAMIN 0 //內部內存池
#define SRAMEX 1 //外部內存池
#define SRAMBANK 2 //定義支持的SRAM塊數.
//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裏面
#define MEM2_BLOCK_SIZE 32 //內存塊大小為32字節
#define MEM2_MAX_SIZE 960 *1024 //最大管理內存960K
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //內存表大小
定義了兩個內存池,一個是內部內存池,一個是外部內存池,在 mallo.c 文件中有對內存池的劃分:
//內存池(32字節對齊)
__align(32) u8 mem1base[MEM1_MAX_SIZE]; //內部SRAM內存池
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000))); //外部SRAM內存池
//內存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //內部SRAM內存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE))); //外部SRAM內存池MAP
//內存管理參數
const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE}; //內存表大小
const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //內存分塊大小
const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //內存總大小
疑問外部內存池指定地址是 0X68000000,是從哪裏得出來的這個地址?
內部內存則由編譯器自動分配。
此部分代碼的核心函數為 mem_malloc 和 mem_free,這兩個函數只是內部調用,外部調用則使用 mymalloc 和 myfree 兩個函數。
我的筆記###
必須要弄懂 malloc_dev 的定義,它在程序內部使用,對用户是透明的,必須要知道它管理這幾塊內存池,及其對應的管理表,弄懂 my_mem_malloc 和 my_mem_free 函數,這兩個函數都是內部使用的。