內存管理基本技術之:邊界標記

內存管理基本技術之:邊界標記

版權聲明:
    本文章由vt.buxiu發佈在
www.vtzone.org,版權歸vtzone研究小組所有,轉載請保持此聲明!!!

@內容摘要:

邊界標記用法最初有Knuth提出,在其計算機算法經典著作《計算機程序設計藝術》中有詳細敘述。經常被應用在支持合併策略的內存分配器中。@
最容易想象到的原始的內存塊:每一塊保存一個塊頭和快尾,爲了維護本塊與前後快的關係,塊頭還保存了兩個指針分別指向前一塊和後一塊。如下圖:








塊頭的結構定義如下:



其實,頭和尾的結構可以是一樣的,也就是說塊頭中的指向前後塊的那兩個指針是可以省略的。下圖省去了塊頭中前後兩個指針,圖中綠色的虛線表示,用於將來合併的兩塊物理是連續的,也就是說第一塊的塊尾後,緊急這是第一塊的塊頭。





這樣一來,頭和尾的結構可以定義爲:

 

struct TTailer{
     size_t size:
31;   //本塊的大小
     size_t used:1;    //標記下一塊是否被使用
}
;
typedef TTailer THeader;

由於邊界標記常用於支持合併策略的分配器中,下面分別描述使用邊界標記方法進行分割和合並的過程。
分割過程:
分割前,初始的一大塊內存如下圖,大小爲2*sizeof(THeader)+size。分割後,分割爲兩個小塊,其中第一塊大小爲sizeof(THeader)+size1,第二塊大小爲2*sizeof(THeader)+size2,當然分割過程必須保證分割前後的內存塊總和是相等的:2*sizeof(THeader)+size = 2*sizeof(THeader)+size1 + 2*sizeof(THeader)+size2










初始的時候我們向系統申請一個size+2*sizeof(THeader)大小的內存。

 

 

//常量定義:4K
const size_t size = 4*1024;      

//向系統申請一塊4k+2*sizeof(THeader)bytes內存
void* raw =  malloc(size+2*sizeof(THeader));

//初始化,設置該塊內存的頭和尾
THeader* header = (THeader*)raw;
header
->size = size;
header
->used = 0;
TTailer
* tailer = (TTailer*)(header + sizeof(THeader) + header->size);
tailer
->size = header->size;
tailer
->used = header->used;

 

分割的時候,將大塊分割爲兩小塊,其中一塊返回給應用滿足其分配請求,其中used標誌設置爲1,另一塊保留下來以備滿足後續的分配請求.

合併過程:合併的過程正好是分割的反操作,爲了說明的完整性,下面給出合併的圖例。







合併操作發生在當應用程序釋放內存塊的時候,先判斷被free的塊的前一塊是否空閒,如果空閒修改前一塊得size直接包含剛剛歸還的塊即可;然後再判斷後一塊是否空閒,如果空閒再修改前一塊size,包含後一塊的大小。
       合併操作代碼:

 

 

 


void Merge(THeader* fisrt, THeader* second)
{
    
//如果合併的兩塊有一個不是空閒的,啥也不幹
    asser(!first->used && !second->used);
    
if(!first->used || !second->used)
        
return;

    
//第一塊塊頭    
    
//直接調整前一個空閒塊的大小
    first->size += (second->size + 2*sizeof(CFirstFitHeader));
    
         
     
//第一塊塊尾
     TTailer* first_block_tailer =( TTailer*)(first + first->size + second->size + sizeof(THeader) + sizeof(TTailer))
     first_block_tailer
->size = first->size;
     
    
return;
}

 

釋放操作,注意,這裏爲了簡化描述,沒有對是否存在前一塊和後一塊做校驗,實際項目中必須做校驗。



很多支持合併的分配器使用邊界標記的方式,每一個內存塊保留一塊頭和塊尾,當請求的平均大小在10字節的時候,頭與尾空間消耗將不能容忍,一個4字節的塊頭將消耗10%空間,尾也浪費10%空間。


 

 


void  Release(void* address)
{
    
//向後移動一個塊頭大小
    THeader* header = static_cast<THeader*>(address - sizeof(THeader));
     
//將該塊設置爲空閒,準備與前後空閒塊進行合併
    header->used = 0;
     
     
//後一塊是否空閒
     THeader* mext_block_header = (THeader*)(header + sizeof(THeader) + sizeof(TTailer) + header->size);
     
if(mext_block_header->used == 0)
     
{
         Merge(header, mext_block_header);
     }


     
//前一塊是否空閒
     TTailer* prev_block_tailer = (TTailer*)(header - sizeof(TTailer));
     
if(prev_block_tailer->used == 0)
     
{
         THeader
* prev_block_header = (THeader*)(prev_block_tailer - sizeof(THeader) - sizeof(TTailer) - prev_block_tailer->size);
         Merge(prev_block_header, header);
     }


    
return;    
}

 

 


void* split(THeader* block, size_t nbytes)
{
     size_t oldsize 
= block->size;
     
if(block->size >= (2*sizeof(THeader) + nbytes)){//將被分割的塊大於分割後的大小+塊頭+快尾
         
//從頭部分割一塊出去
         
//設置第一塊頭
         block->size = nbytes;                       
         block
->used = 1;
         
//設置第一塊尾
         TTailer* first_block_tailer = (TTailer*)(block + sizeof(THeader) + nbytes);
         first_block_tailer
->size = nbytes;
         first_block_tailer
->used = 1;

         
//調整第二塊
         
//設置第二塊頭
         THeader* second_block_header = (THeader*)(block + 2*sizeof(THeader) + nbytes);
        second_block_header
->size = oldsize - (2*sizeof(THeader) + nbytes);
        second_block_header
->used = 0;
         
//設置第二塊尾
         TTailer* second_block_tailer = (TTailer*)(second_block_header + sizeof(THeader) + second_block_header->size);
        second_block_tailer
->size = second_block_header->size;
        second_block_tailer
->used = second_block_header->used;
         
return (void*)(block + sizeof(THeader));
     }


     
//內存塊不夠大,無法分割
    return NULL;
}

 

 

 

 

 

複製內容到剪貼板
struct TTailer...{
     size_t size:
31;   //本塊的大小
     size_t used:1;    //標記下一塊是否被使用
}
;

struct THeader{
     size_t size:
31;   //本塊的大小
     size_t used:1;    //標記上一塊是否被使用
     TTailer * prev;   //指向前一個塊的頭
     THeader* next;    //指向下一個塊的頭
}
;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章