引言

內存池作為一種的內存管理機制被廣泛地運用於各種領域當中,內存池擁有快速的內存分配與更加健壯的管理機制,同時在不同的平臺與環境當中也擁有不同的實現方式,本文提出一種輕量級的內存池實現,可以非常方便的移植到內存空間較小的平臺當中,可運用在不同的嵌入式

平臺,服務端及小範圍的內存管理當中.

從內存開始

巧婦難為無米之炊,既然我們要說是內存池,顯然你首先至少需要找到一個能存儲數據的東西,否者接下來都沒有討論的意義,存儲的意義可以非常的廣泛,它可以是名正言順的RAM,也可以是你的硬碟,甚至是你的顯存或者SD卡當中,為了給PainterEngine的內存池指定內存地址,你需要預先給出一塊可用內存的大小與及它的起始地址.例如下面的代碼將會給你做一個示範

//為內存池分配1M的內存空間,MemoryPool指向首地址

void *MemoryPool=malloc(1024)

//MemoryPool指向地址0x08600000地址,這塊地址可能指向一個片外RAM,一塊NANDFlash地址或者其它的任意存儲設備

void *MemoryPool=(unsigned char*)0x08600000;

//MemoryPool可存在於堆中也可存在於棧當中,但是你需要確保它的地址在內存池的使用階段都是有效的。

char MemoryPool[1024];

可以看到,PainterEngine的內存池可以存在於任何的地址與區域當中,你需要確保的只有三點,1.內存地址是線性的,2.內存是可讀的 3.內存是可寫的.

一塊內存

取得一塊內存區域並不是什麼非常困難的事情,在windows或者是linux當中,你可以使用現有的malloc或者C++的new來申請一塊可用的內存地址,在嵌入式設備當中,你也可以使用片上RAM或者是類似於FSMC或外部地址匯流排等方式來指定一塊內存,具體如何實現由您決定。

那麼現在我們假設你已經擁有了這一塊可用的內存並且做好了其它的一些別的準備工作,為了方便,我們假設您的內存地址是從0x00000000開始的,並且我們假設它的大小是

Size,為了說明方便我們使用圖1來表示。

圖1

從上面來看我們已經初始化一些基本的數據並且可以推導一些有用的數據,首先我們有一塊內存,而且它的大小是Size,當中的所有區域我們都是可用的。我們還知道它的結束地址,是0x00000000+size-1;

那麼既然是內存池,最基本的一個功能當然是可以從內存池當中分割出一塊內存來供我們使用,這有點兒像分餅乾,假如你讀過一些內存池相關的文章,也許現在我們普遍的做法就是把這一塊內存區域均等的分為多個block,比如假如這裡的size是512位元組,假如每個block佔64位元組,那麼這個內存區域就會被分為8個block,當然還有更進一步,4個block組成一個chunk,那麼512位元組的內存區域,就包含有2個chunk或者說包含有8個block,當然,這裡僅僅只是用512位元組作為一個比方,也許你覺得這樣分是在是多此一舉,但假如這個size大到64M或者是128M,那麼,用一個chunk為單位,對內存池進行定址,顯然會比你用位元組為單位進行定址要快得多,並且定址範圍也會大的多,並且以chunk(或者別的更大的單位)進行內存管理,對內存碎片的整理合併也會方便且快得多而且很容易使用map進行映射,這樣就能夠非常快的定址處理,這種思想被廣泛的運用在了文件系統與內存管理方面,例如windows操作系統就會建立一個頁目錄及頁表來管理其內存空間,採用這種映射方式,它允許其內存在物理上不是連續的,甚至映射到硬碟或其他存儲設備當中(虛擬內存)。

分割一塊可用的內存

但遺憾的是,PainterEngine並不將內存進行這種分割處理,我們可以假如我們將一個Block分割為64位元組,假設我需要一個86位元組的內存空間,那麼我們需要2個block也就是128位元組,因為block作為一個整體是不可分的,那麼我們將有42位元組被白白的浪費了,在內存以G為單位的PC機上,也許這些損失實在是無足輕重,然而對於僅僅只有幾百kb甚至是幾kb的片上系統來說,這些損失就顯得實在是太過於昂貴了。你也許會考慮到進一步縮小block提高細分度來減少這種損失,但是,即便是對block進行的定址,也是需要內存空間的,並且更小的細粒度除了浪費內存之外,也顯得毫無意義

技術不是扯淡,更不是拿起一本書就開始照本宣科,脫離實際地區域爭論某種高深架構或者方法如何優秀,只是為了掩飾自己能力的貧庸.我們再來看看PainterEngine面向的環境.

  1. 輕量級內存池,首先決定了它不能過於的臃腫與龐大,在保證功能的前提之下,能多簡單就多簡單,沒必要把簡單的問題複雜化
  2. 內存分配較小。這就意味著使用映射表的方法意義實在不大,也許10000*10000的複雜度會很大的優於100lg100,但是10lg10和10*10的差別確實不怎麼大,即使我們建立的內存表都用鏈表來進行管理,因為內存較小,表也不大,即使是遍歷也未必會比映射慢上多少
  3. 假如我們要記錄一塊分配內存,至少需要哪些信息呢,一.這塊內存的起始地址

二.這塊內存的結束地址或者它的大小。

那麼,問題就開始變得簡單了,假如內存池是一條等待切割的繩子,現在一個人想要3米長的繩子,那麼非常簡單,0-3米我們規定就是他的了,又一個人想要2米的繩子,那麼我們再規定3m-5m這一段距離的繩子就屬於它的了。為了記錄下一次我們將從哪裡開始切割繩子並且我們也需要知道剩下繩子的長度,以免我們只剩下1m的繩子了仍然答應一個想要2m長繩子的傢伙,因此我們需要2個變數來進行記錄1.下一次分割繩子的開始點,2.剩餘繩子的長度。也就是下一次分配內存的起始地址和剩餘內存的大小。那麼,最基本的內存分配就這樣實現了

我們先定義內存池的基本結構

typedef struct _MemoryPool
{
void *AllocAddr;//下一次內存的分配地址
void *EndAddr;//內存池的尾地址
unsigned int Size;//內存池的總大小
unsigned int FreeSize;//剩餘內存池空間大小
}MemoryPool;

我們定義一個函數,用來初始化這個內存池
void CreateMemoryPool(void *MemoryAddr,unsigned int MemorySize);
它的實現是
void CreateMemoryPool( void *MemoryAddr,unsigned int MemorySize )
{
G_MemoryPool.AllocAddr=MemoryAddr;//最開始的分配地址就是起始地址
G_MemoryPool.EndAddr=((unsigned char*)MemoryAddr)+MemorySize-1;//計算出內存池終止地址
G_MemoryPool.Size=MemorySize;//內存池大小
G_MemoryPool.FreeSize=MemorySize;//一開始剩餘內存池空間大小就是內存池大小
}

下面幾張圖表示內存的分配情況

  1. 內存初始化時

  1. 分配內存A後

  1. 分配內存B後

有借有還:內存碎片與回收

記錄分配節點

計算機是一個偉大的發明,毫無意味的在某些數學角度方面,它的計算速度要比人快得多,但是遺憾的是,計算機未必比人聰明,而且是一個奇怪的傢伙,他記憶力很很差勁,只能靠一些存儲器件來幫助他進行記憶,比如寄存器和硬碟。

上面的方案看上去似乎工作的非常好,而且也不會出上面問題,當然,假如你的內存是無限大的話,你完全可以這麼幹,然而在某些平臺上,你卻不得不摳門地去節省那幾個位元組甚至是幾個位,上面的方案顯然碰到了一些問題,我們分配了AB兩個內存塊,也許A當中只是存儲了一些臨時的數據,或者說這些數據我們用不到了,我們也許會希望釋放掉這個內存,並且把空閑的內存歸還到內存池。

然而上面的方案我們只是把內存簡簡單單的劃分出去了,而沒有做任何記錄,顯然的,我們應該給它做個記錄,為了節省內存,我們盡量將記錄變得簡略,這並不複雜,我們只要記錄分配的起始地址和結束地址就可以了.就像出租房房東一樣,在賬本下記下幾號房到幾號房出租給了某某某,當那人來退房的時候,那幾個房間就可以回收利用了,在這裡,我們將這個記錄稱為內存節點,用c語言表示如下:

typedef struct _MemoryNode
{
void *StartAddr;
void *EndAddr;
}MemoryNode;

如何放置內存節點

上面的思路也許看上去非常簡單,不需要太多的思考也能想出來,但是如何放置內存節點和如何回收回來的內存節點確值得思考,在網上流行的很多內存池中,基本都採用了鏈表的方式去連接和管理節點,假如新增加一個節點,就使用malloc分配一個,然而在一個要求高度可移植的內存池方面,這個想法卻是不完善的,首先,你不知道堆到底有多少空間供你分配,因為非常多的小型系統的堆大小僅僅只有可憐的幾kb,而且用戶很可能已經將那些空間使用的所剩無幾了,所以這個辦法顯然是被否決的,另一種辦法是採用類似於windows的內存管理機制,使用頁表,但之前也已經提及了,這種辦法是在是太奢侈了,另外,放置節點的位置也需要講究,我們看看釋放內存的函數

Free(void *address)

Address是需要釋放內存的首地址,這也意味著,你需要依靠首地址去尋找這個節點,假如你將內存節點都存儲在一個區域當中,你很可能要去遍歷來尋找這個節點,倘若你調用分配函數1000次,每次free都有可能遍歷節點數百次,這顯然對於cpu而言,實在是太過的奢侈繁瑣了。

因此最合適的位置,應該是分配到address福建,將節點放在開始地址的前面,應該是一個不錯的方案。

使用sizeof(MemoryNode)我們很容易確定內存節點的大小,所以在free的時候,我們僅僅需要將address減去sizeof(MemoryNode),我們就可以非常容易獲得這個內存節點,並且根據這個節點來取得內存數據區的起始位置和結束位置。

釋放的內存節點與管理

現在,我們可以將那些釋放掉的內存節點進行管理了,我們仍然需要將釋放的內存節點存儲在內存池當中,但我們將不採用鏈表的形式存儲,相對於鏈表,線性存儲顯然頗具優勢。而我們需要做的,僅僅只是為其挑一個存儲的位置與空間,不言而喻的,將這些釋放的節點存儲在內存池的尾部,是一個不錯的選擇,因為受到池內存大小的限制,釋放的內存節點經過整理後也不會顯得過於的龐大,因此,即使是遍歷搜索也不會慢到哪裡去,釋放內存節點非常簡單,我們僅僅只需要將節點的起始地址設置為內存節點的開始地址,然後複製節點節點到尾部就可以了,就像下圖所表示的那樣:

釋放第一塊內存:

1.拷貝節點到內存池尾部

2.實際上無需其它的操作,這塊內存意義上已經釋放了

我們用下面的代碼,簡單為內存池增加一個釋放的內存節點:

MemoryNode *PX_AllocFreeMemoryNode()
{
//增加釋放的節點數目
G_MemoryPool.FreeTableCount++;
//以MemoryNode指針的形式返回該內存節點的首地址
return PX_MemoryPool_GetFreeTable(G_MemoryPool.FreeTableCount-1);
}

不能失敗的內存釋放

但是上面的辦法真的沒有問題嗎,我們看看Free的原型

Void Free(void *Address)它的返回值是null也就是必須成功,那麼什麼時候上面的方案會失敗呢,非常明顯的,每當釋放一個內存節點,我們將從尾部去記錄釋放的內存節點,尾部的空間實際上是輸入FreeSize的,當FreeSize小於一個內存節點的大小的時候,分配就會失敗

(FreeSize已經不足以保存一個節點,釋放失敗)

釋放失敗的後宮是嚴重的,它將導致那塊內存被永久的佔用,比如你內存池大小是1M,已經分配了1023位元組,那麼,這個內存池除非你恰好釋放的是最後一個節點(稍後討論),否者這個內存池將直接陷入癱瘓,但是辦法總是有的,其中最有效的是,我們在內存分配期間,就需要為內存的釋放預留出空間,這個雖然稍微有點兒浪費空間,但是這對內存池的健康與維護大有好處,並且MemoryNode結構本身並不大,釋放空間操作相比於分配操作往往要少的多,絕大部分的空間都是隨著內存分配的開始到程序運行的結束。

那麼這也就意味著,在我們分配內存的時候,至少需要分配兩個MemoryNode節點大小的內存空間纔能夠滿足要求,同時我們也需要記錄下當前有多少個已經釋放的內存節點與在釋放的節點中最大的節點大小是多少,這樣做方便後期的查找與遍歷與在釋放的內存節點中分配新的內存,我們修改MemoryPool結構體:

typedef struct _MemoryPool
{
void *AllocAddr; //FreeSize內存區域起始地址
void *EndAddr; //內存池終止地址
unsigned int Size;//內存池大小
unsigned int FreeSize;//FreeSize生於大小
unsigned int FreeTableCount;//釋放節點的數目
unsigned int MaxMemoryfragSize;//釋放節點中最大的碎片大小
}MemoryPool;

同時,從FreeSize中分配內存的實現的代碼就如下所示
MemoryNode *Node;
//檢查剩餘空間是否足夠分配,大小是2倍的memorynode大小加上分配大小
if (Size+2*sizeof(MemoryNode)>G_MemoryPool.FreeSize)
{
return 0;
}
//初始化MemoryNode
Node=(MemoryNode *)G_MemoryPool.AllocAddr;
//指定數據的開始地址
(*Node).StartAddr=(unsigned char )G_MemoryPool.AllocAddr+sizeof(MemoryNode);
//指定數據結束地址
(*Node).EndAddr=(unsigned char *)Node->StartAddr+Size-1;
//FreeSize的大小減去分配的空間大小
G_MemoryPool.FreeSize-=(Size+2*sizeof(MemoryNode));
//將AllocAddr移到新的位置
G_MemoryPool.AllocAddr=(char*)G_MemoryPool.AllocAddr+Size+sizeof(MemoryNode);
//返回內存節點指針
return Node;

內存的碎片與減少碎片

也許最合理的內存回收是能夠回收多少是多少,回收完後仍然是線性的,例如現在我釋放 了一個大小為A(8位元組) B(12位元組) C(4位元組)的內存,下次我需要分配24位元組,可以直接從之前釋放的空間中申請,但是,實際情況上卻常常難以做到,因為我們申請的內存,必須是線性連續分配的,但釋放後的內存空間,往往不是連續的.

(非連續內存導致碎片)

為了最大程度地減少碎片,我們需要將那些原本連續的碎片進行拼接合併,例如內存節點A的結束地址和內存節點B的開始地址恰好是重合的,那麼我們就可以考慮將節點A與節點B進行合併,合併內存不論是在效率上還是內存的再分配上都有好處.

我們對以下幾種情況的內存碎片分別討論

  1. 不能合併的內存節點

A是一個釋放的內存節點,很遺憾的是,不論是A的前方或者後方都是一家被內存池分配出去的內存,A將不能進行合併,對於這種情況,調用之前寫好的PX_AllocFreeMemoryNode函數分配一個節點,並將A的內存節點信息直接保存在內存池的尾部

  1. 可以向前合併的節點

A是一個待釋放的內存節點,B是一個已釋放的的內存節點,可以看到,BA在內存空間上是相連的,所以我們需要將BA兩個內存空間進行合併,在這個過程當中,我們不需要再分配一個釋放的內存節點,原先為A釋放節點的預留空間也可以刪除回歸到Freesize當中,之後僅需要修改原來就存在的B的釋放內存節點,將他的結束地址修改為節點A的結束地址,就可以完成釋放了。

  1. 向後合併的節點

A是一個待釋放的內存節點,B是一個已釋放的的內存節點, AB在內存空間上是相連的,所以我們需要將AB兩個內存空間進行合併,其過程與向前合併時類似的。但是向後合併我們還可以更進一步地進行優化,來看看下面這種情況

在節點A的後面,就是AllocAddr,在這種情況下,我們可以盡量地擴大FreeSize而不是將A的節點放入釋放內存節點中,我們直接將AllocAddr放到A的首地址,並且刪除原先為A的預留空間,這樣,A就被釋放掉了

  1. 前後合併節點

前後合併的其實就是向前合併和向後合併的組合,但我們需要的是注意一下合併的順序,因為向後合併存在一種特殊的情況,我們可以將其選擇出來並進行專門的處理,就如下圖所示的情況

第一種情況的前後合併時,需要刪除一個預留空間(看順序)並且刪除釋放節點,最後只留下一個節點,第二種情況首先向前合併,同時刪除B的釋放內存節點和A的預留空間,將AllocAddr移動到B的起始地址完成內存釋放.使用下面的代碼實現刪除釋放內存節點

__inline MemoryNode *PX_MemoryPool_GetFreeTable(unsigned int Index)
{
//index不可能大於等於節點數目,下斷言
assert(Index<G_MemoryPool.FreeTableCount);
//索引加1
Index++;
//向前偏移返回memoryNode的地址
return (MemoryNode *)((unsigned char *)G_MemoryPool.EndAddr-(sizeof(MemoryNode)*Index)+1);
}

void PX_MemoryPoolRemoveFreeNode(unsigned int Index)
{
unsigned int i;
MemoryNode *pFreeNode=PX_MemoryPool_GetFreeTable(Index);
//將之前的節點前移實現移除這個節點
for (i=Index;i<G_MemoryPool.FreeTableCount;i++)
{
(*pFreeNode)=*(pFreeNode-1);
pFreeNode--;
}
//釋放內存節點數減1
G_MemoryPool.FreeTableCount--;
}
最後,依照上述的幾種情況編寫內存釋放函數

void PX_Free( void *pAddress )
{
unsigned int i,sIndex;
MemoryNode *itNode;//迭代指針
MemoryNode FreeNode;//釋放內存節點
unsigned char *pcTempStart,*pcTempEnd;
unsigned char bExist;

bExist=0;
//依據地址得到內存信息節點
FreeNode=*(MemoryNode *)((unsigned char *)pAddress-sizeof(MemoryNode));
//重置內存節點的開始位置
FreeNode.StartAddr=(unsigned char *)FreeNode.StartAddr-sizeof(MemoryNode);

//假如這個節點是最後一個節點,也就是結束地址後一個就是AllocAddr
if ((char *)FreeNode.EndAddr+1==(char *)G_MemoryPool.AllocAddr)
{
//向前搜索是否有可以拼接的節點
for(i=0;i<G_MemoryPool.FreeTableCount;i++)
{
itNode=PX_MemoryPool_GetFreeTable(i);
if ((char *)itNode->EndAddr+1==(char *)FreeNode.StartAddr)
{
//如果有,將AllocAddr的地址移到那個節點的開始地址
G_MemoryPool.AllocAddr=itNode->StartAddr;
//恢復釋放節點的空間到FreeSize同時刪除預留空間的地址
G_MemoryPool.FreeSize+=((char*)FreeNode.EndAddr-(char *)FreeNode.StartAddr+sizeof(MemoryNode)+1);
//恢複合並的空間到FreeSize
G_MemoryPool.FreeSize+=(char*)itNode->EndAddr-(char *)itNode->StartAddr+1+sizeof(MemoryNode);
//移除這個節點
PX_MemoryPoolRemoveFreeNode(i);
//處理完成,返回
return;

}
}
//如果沒有需要拼接的節點,將AllocAddr直接連接到起始地址
G_MemoryPool.AllocAddr=(char *)FreeNode.StartAddr;
//恢復佔用的空間和預留空間
G_MemoryPool.FreeSize+=((char*)FreeNode.EndAddr-(char *)FreeNode.StartAddr+sizeof(MemoryNode)+1);
return;
}

//不是特殊情況

//記錄前一次連接索引的節點,若為0xfffffff表示沒有,如果有下一次連接節點且上一個節點存在,則刪除前一次的節點
sIndex=0xffffffff;

//查找是否有可連接的節點
for(i=0;i<G_MemoryPool.FreeTableCount;i++)
{
itNode=PX_MemoryPool_GetFreeTable(i);
pcTempStart=(unsigned char *)itNode->StartAddr;
pcTempEnd=(unsigned char *)itNode->EndAddr;
//查找是否有可以向後連接的節點
if (pcTempStart==(unsigned char *)FreeNode.EndAddr+1)
{
//如果有,按照之前所述連接節點
if(sIndex==0xffffffff)
{
sIndex=i;
//Refresh this node
itNode->StartAddr=FreeNode.StartAddr;
FreeNode=*itNode;
G_MemoryPool.FreeSize+=sizeof(MemoryNode); }
else
{
itNode->StartAddr=FreeNode.StartAddr;
G_MemoryPool.FreeSize+=sizeof(MemoryNode);
PX_MemoryPoolRemoveFreeNode(sIndex);
}
bExist=1;
}

//查找是否有可以向前連接的節點
if (pcTempEnd+1==(unsigned char *)FreeNode.StartAddr)
{
//如果有,按照之前所述連接節點
if(sIndex==0xffffffff)
{
sIndex=i;
//Refresh this node
itNode->EndAddr=FreeNode.EndAddr;
FreeNode=*itNode;
//移除上一個節點
G_MemoryPool.FreeSize+=sizeof(MemoryNode);
}
else
{
itNode->EndAddr=FreeNode.EndAddr;
G_MemoryPool.FreeSize+=sizeof(MemoryNode);
//移除上一個節點
PX_MemoryPoolRemoveFreeNode(sIndex);
}
bExist=1;
}
}
//如果沒有連接節點,分配釋放內存節點
if(bExist==0)
*PX_AllocFreeMemoryNode()=FreeNode;

//這個函數用於更新最大的釋放內存節點大小
PX_UpdateMaxFreqSize();

}

分配內存

從空閑空間中分配內存

在上一個章節的」 分割一塊可用的內存」,」不能失敗的內存釋放」中,給出了這個空閑空間分配內存的方法與分配代碼,在這裡就不再複述了,它的流程非常簡單

  1. 分割出一塊合適的空間大小
  2. 初始化內存節點
  3. 預留空間
  4. 返回起始地址

短短几步,就能夠完成空閑空間的內存分配,當內存空間不夠時,返回0

從已釋放內存中分配內存

回收內存再分配,是一個內存池能否能夠持續良好持續運行的一個必要功能,同時,這也常常成為碎片產生的原因.但是內存池是否能夠完全避免產生碎片呢,當然這並不是不可能的,但是付出的代價往往不值得我們這樣去做.所以,內存碎片,儘可能的減少卻不能夠完全避免.

那麼應該如何減少碎片呢,其實這和環境與用法有很大的不同,一個老練的碼農往往喜歡把內存分配到2的整數冪,這也對減少碎片有很大的幫助,比如你分配了一個64位元組大小的內存釋放之後又分配一個32位元組和4個8位元組的內存,剛好就填充了之前的空間,越小的內存分配越容易填充之前的碎片,當然,假如你申請的內存和釋放的內存一直保持一致的大小,這個碎片也就能夠最大程度的消除了.

過多的碎片不僅佔用了不必要的內存空間,也拖慢了再分配和釋放,因此,內存分配並不僅僅只是演算法上的,很多時候也是使用和習慣上的.

那麼如果需要從已分配內存當中新分配內存應該怎麼辦呢,我們先來討論一下不產生碎片的情況,最直觀的是,你釋放了一個內存的大小,剛好就是你想再分配內存的大小,這是無縫的,自然不會產生碎片.但是我們不能就僅僅止步於此,我們再來看看內存的結構,

是的,在PainterEngine內存池結構中,分配一個size大小的內存,需要size+2*sizeof(MemoryNode)大小的內存空間,分配內存分配的大小,是我們主觀上的大小,我們為其多分配一些內存空間,並不會出什麼大不了的問題,比如你申請 16位元組大小,而我分配了32位元組給你,多餘的位元組並不會對其它功能造成影響,頂多我不知道不用就是了,而且這樣比我們分割這個已經釋放的內存空間更有好處,畢竟,為新劃分的空間需要預留額外的sizeof(MemoryNode)大小的空間而且也會照成碎片,那麼,我們可以這樣規定

假如已經釋放的內存大小大於等於size並且小於sizeof(MemoryNode)+size,我們就將這個已經釋放的內存全部分配給需求的內存.

當然,我們不可能期待每個已經釋放內存都像上面一樣,很多時候,我們釋放了大小為128位元組的空間,下一次卻只需要32位元組的內存空間,把這個128位元組的釋放空間都分配給這個只需要32位元組的內存節點,顯然是太過的浪費了,這個時候,我們必須要對內存進行分割

分割並不複雜,只需要把原先的釋放內存首地址作為分配內存的首地址,填充MemoryNode節點,然後將結束地址+1替換到原來的釋放內存節點的首地址就可以了,同時別忘了預留一個內存節點的空間.

MemoryNode *PX_AllocFromFreq(unsigned int Size)
{
unsigned int i,fSize;
MemoryNode *itNode,*allocNode;

//大小加上頭部內存節點需要空間
Size+=sizeof(MemoryNode);
//比對最大已釋放內存大小,若小於,分配肯定是失敗的
if (G_MemoryPool.MaxMemoryfragSize>=Size)
{
//開始搜索滿足條件的節點
for(i=0;i<G_MemoryPool.FreeTableCount;i++)
{

itNode=PX_MemoryPool_GetFreeTable(i);
//計算節點大小
fSize=(char *)itNode->EndAddr-(char *)itNode->StartAddr+1;
//滿足內存節點完全分配的情況
if (Size<=fSize&&(Size+sizeof(MemoryNode)>fSize))
{
//初始化新的內存節點
allocNode=(MemoryNode *)itNode->StartAddr;
allocNode->StartAddr=(char *)itNode->StartAddr+sizeof(MemoryNode);
allocNode->EndAddr=itNode->EndAddr;
//因為是完全分配,直接刪除原來的節點,因為空間已經預留過了,所以不需要預留空間
PX_MemoryPoolRemoveFreeNode(i);
//重新跟新最大釋放內存節點大小
PX_UpdateMaxFreqSize();
return allocNode;
}
else
{
//不滿足完全分配的情況,開始進行內存分割
if(Size<fSize)
{
//確認FreeSize足夠分配一個內存節點
if (G_MemoryPool.FreeSize<sizeof(MemoryNode))
{
return 0;
}
//初始化內存節點
allocNode=(MemoryNode *)itNode->StartAddr;
allocNode->StartAddr=(char*)itNode->StartAddr+sizeof(MemoryNode);
allocNode->EndAddr=(char *)itNode->StartAddr+Size-1;
//重新計算釋放內存節點的起始位置,完成分割
itNode->StartAddr=(char *)allocNode->EndAddr+1;
//預留空間
G_MemoryPool.FreeSize-=sizeof(MemoryNode);
//再次重新計算最大釋放內存節點大小
PX_UpdateMaxFreqSize();
return allocNode;
}
}
}
return 0;
}
else
{
return 0;
}
}

推薦閱讀:

相關文章