2009年2月9日星期一

Nebula3内存模块-内存池(二)

在上一篇blog中我们已经讨论完关于内存池的初始化,这里我们来看看是如何从内存池中取得所需的内存块。要从内存池中分配一块内存块,我们需要调用void* MemoryPool::Alloc()方法,接下来我们详细介绍该方法是如何执行的:

1.首先判断存放可分配内存块的内存页的数组roomyPages是否为空,如果为空就调用void MemoryPool::AllocPage()创建一个新的内存页。

if (this->roomyPages.IsEmpty())
{
// all pages are full, or no pages exist yet, need to allocate new page
this->AllocPage();
}

2.从roomyPages中取得一个内存页,并调用void* MemoryPoolPage::Alloc()方法实际分配一块内存块。

MemoryPoolPage* page = this->roomyPages.Back();
n_assert(page->GetNumFreeBlocks() > 0);

// note: the page pointer is guaranteed to be the one
// from the end of the roomyPages array!
void* ptr = page->Alloc();

3.判断刚才取得的内存页是否还有可以分配的内存块。如果没有就把它从roomyPages中删除掉。

if (0 == page->GetNumFreeBlocks())
{
this->roomyPages.EraseIndex(this->roomyPages.Size() - 1);
}

4.返回指向刚才分配的内存块的指针。

我们更加详细的看一下void* MemoryPoolPage::Alloc()是如何分配一个内存块的。

1.找第一个还没被使用的内存块,并把该内存块块头的pageObject指向本内存页,代码如下:

BlockIndex newBlockIndex = this->firstFreeBlockIndex;
BlockHeader* newBlock = this->BlockIndexToPointer(newBlockIndex);
n_assert(0 == newBlock->pageObject);
newBlock->pageObject = this;

2.设置该内存块的前置和后置,让已分配的内存块和未分配的内存块通过前置和后置关系形成两个逻辑上的列表。并让firstFreeBlockIndex索引指向未分配内存块逻辑列表的第一位,firstAllocBlockIndex索引指向已分配内存块逻辑列表的第一位。并且把当前已分配的内存块放在已分配逻辑列表的第一位。

this->RemoveBlockFromList(this->firstFreeBlockIndex, newBlockIndex);
this->InsertBlockIntoList(this->firstAllocBlockIndex, newBlockIndex);

3.移动指针,让指针指向内存块体。

void* dataPtr = (void*) (newBlock + 1);

4.把已分配内存块的记录数numAllocBlocks加1,返回指向内存块体的指针。

this->numAllocBlocks++;

以上就完成了从内存池中分配一个内存块。接下来我们探讨一下如何释放一个内存块。

我们调用void MemoryPool::Free(void* ptr)来释放一个从内存池分配的内存块。执行步骤如下:

1.通过传入的指针,我们找到分配该内存块的内存页。

MemoryPoolPage* page = MemoryPoolPage::GetMemoryPoolPageFromDataPointer(ptr);

2.调用内存页的void MemoryPoolPage::Free(void* ptr)来真正释放掉内存块。

3.如果该内存页包含一个可以分配的内存块就把该内存页放入roomyPages中。如果该内存页所有的内存块都没被分配,并且内存不止一个内存页,那么就把该内存页从内存池中删除掉。

if (page->GetNumFreeBlocks() == 1)
{
// we've been full previously, but are roomy now
this->roomyPages.Append(page);
}
else if (page->GetNumAllocatedBlocks() == 0)
{
// the page no longer contains any allocated blocks, free
// the entire page, unless this is the last page to
// prevent expensive allocation/deallocations if only
// one block is allocated/freed in this memory pool
if (this->pages.Size() > 1)
{
this->FreePage(page);
}
}

void
MemoryPool::FreePage(MemoryPoolPage* page)
{
n_assert(page->GetNumAllocatedBlocks() == 0);
IndexT pagesIndex = this->pages.FindIndex(page);
n_assert(pagesIndex != InvalidIndex);
IndexT roomyPagesIndex = this->roomyPages.FindIndex(page);
n_assert(roomyPagesIndex != InvalidIndex);

// note: we don't use EraseIndexSwap to keep the page order
// in the order of allocations
this->pages.EraseIndex(pagesIndex);
this->roomyPages.EraseIndex(roomyPagesIndex);
delete page;
}

同样接下我们看看内存页是如何释放掉内存块的void MemoryPoolPage::Free(void* ptr)。

1.取得要删除内存块的索引。

BlockHeader* block = ((BlockHeader*)ptr) - 1;
this->VerifyBlockHeader(block);
BlockIndex blockIndex = this->BlockPointerToIndex(block);

2.把该内存块块头pageObject指针设置为0。

n_assert(this == block->pageObject);
block->pageObject = 0;

3.把该内存块从已分配内存块的逻辑列表断开,并把它放入未分配内存块逻辑列表中。

this->RemoveBlockFromList(this->firstAllocBlockIndex, blockIndex);
this->InsertBlockIntoList(this->firstFreeBlockIndex, blockIndex);

4.已分配内存块的数量减1。

this->numAllocBlocks--;

以上就是n3内存池实现的一个大概讨论。

没有评论:

发表评论