内存管理基本技术之:边界标记

Posted ouyangbuxiu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存管理基本技术之:边界标记相关的知识,希望对你有一定的参考价值。

内存管理基本技术之:边界标记

版权声明:
    本文章由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;    //指向下一个块的头
}
;

 

以上是关于内存管理基本技术之:边界标记的主要内容,如果未能解决你的问题,请参考以下文章

内存管理基本技术之:边界标记

Linux 0.11 - 管理内存前先划分出三个边界值-12

测开之内存管理篇・《内存管理机制》

日更第17日: (翻)nginx加固之防御缓冲区溢出攻击

你必须了解的java内存管理机制-垃圾标记

JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理)