APR内存池的学习与实现

APR内存池的学习与实现

在很早之前,本人在学习Apache中的内存池的实现的时候,尝试过仿照其重新简单实现了下。主要目的是更快得理解它。这篇文章也是基于当初我自己重新写了一遍的代码,其原理是一致的。同时也参考了《APR内存池概述》这篇文章,并修正了其中一些错误之处。

那么关于什么是内存池,内存池有哪些好处,本文就不赘述了。直接进入正题吧


内存节点

在了解池之前,首先得介绍节点。为了方便地对所有已经分配的内存进行管理,用内存节点的概念来描述每次分配的内存块,其定义如下:

struct _MEMNODE
{
	struct _MEMNODE			*next;
	struct _MEMNODE			**ref;
	unsigned int			index;
	unsigned int			free_index;
	char					*first_avail;
	char					*endp;
};

_MEMNODE是内存管理的基石,在后文中,我们称之为“内存节点”或“节点”。
从上面的结构中可以看出在_MEMNODE内部没有任何的"空闲空间"来容纳实际分配的内存。事实上也确实如此,它从不单独存在,一旦分配了实际的空间之后,该结构将置于最顶部,如下图所示:
内存节点示意图

first_availend_p

在上图中,假如我们分配了一定大小的空间,为了能够将该空间用节点进行记录,我们将_MEMNODE置于整个空间的头部,则实际可用空间为剩下的空白部分,同时结构中还提供了first_availend_p指针分别指向这块可用空间的首部和尾部。当这块可用空间被不断利用时,first_avail指针也不断随之移动,而end_p指针是永远指向尾部不动的,不过(end_p-first_avail)之间则永远是当前的空闲空间。上图的右边部分演示了这种布局。

indexfree_index

Apache中对内存的分配大小并不是随意的,随意的分配可能会造成更多的内存碎片。为此Apache采取的则是“规则块”分配原则。Apache所支持的分配的最小空间是8K,如果分配的空间达不到8K的大小,则按照8K去分配;如果需要的空间超过8K,则将分配的空间往上调整为4K的倍数。

即:如果需要分配的空间(包括节点头的空间)总和小于8K,则以8K进行分配,否则调整为4K的整数倍。

举个例子:如果我们要求分配的size大小为1024,其按照最小单元分配,实际分配大小为8192;如果我们要求分配的空间为8192,由于其加上内存节点头,大于8192,此时将按照最小单元分配4k,此时实际分配的空间大小为8192+4096=12K。

那么这样一来,每个节点的空间大小都不完全一样,为此分配节点本身必须了解本节点的大小,于是使用index进行记录。需要注意的是,index只是记录当前节点大小相对于4K的倍数,计算方法为:

index=(size>>BOUNDARY_INDEX)-1; //BOUNDARY_INDEX=12,即4K。

这样如果index=5,我们就可以知道该节点大小为24K;反过来也是如此。

这样将大小转化为index是为了方便程序处理(后面会详细提到),我们将该值称之为“索引大小”,与此相同,free_index则是定义了当前节点中可用空间的索引大小。

nextref

额,关于这两个成员,大家就顾名思义吧。就不再赘述了。不同的节点之间通过next指针形成节点链表;另外当在节点内部的时候ref为了方便引用节点本身。

相关代码

相关宏定义:

#define MEM_ALIGN(size,boundary) (((size) + ((boundary)-1)) & ~((boundary)-1))
#define MEM_ALIGN_DEFAULT(size) MEM_ALIGN(size,8)
#define BOUNDARY_INDEX	12
#define MAX_INDEX		20

相关常量:

const unsigned int BOUNDARY_SIZE 	= 1 << BOUNDARY_INDEX;	//4096
const unsigned int MIN_ALLOC 		= 2 * BOUNDARY_SIZE;	//8192
const unsigned int SIZEOF_MEMNODE 	= MEM_ALIGN_DEFAULT(sizeof(MEMNODE));//MEMNODE大小

节点分配函数部分代码:

MEMNODE *Allocator_alloc(ALLOCATOR *allocator, size_t in_size)
{
    MEMNODE *node, **ref;
    unsigned int max_index;
    size_t size, i, index;

    //4096的倍数,不满8192,则分配8192
    size = MEM_ALIGN(in_size + SIZEOF_MEMNODE, BOUNDARY_SIZE);
    if (size < in_size)	return NULL;
    if (size < MIN_ALLOC)	size = MIN_ALLOC;

    //将大小转为下标
    index = (size >> BOUNDARY_INDEX) - 1;

    //中间代码暂时省略,文章后面慢慢补齐的同时再做介绍
    //.....
    //
    
    if ((node = (MEMNODE*)malloc(size)) == NULL)
        return NULL;

    node->next = NULL;
    node->index = index;
    node->first_avail = (char *)node + SIZEOF_MEMNODE;
    node->endp = (char *)node + size;

    return node;
}

内存分配子

那么同样的道理,为了方便分配和管理内存节点_MEMNODE,Apache引入了一个叫做allocator的东西,姑且就叫它内存分配子吧,细心的你可能已经在上文的相关代码里见到了它,那么这个结构的定义如下:

struct _ALLOCATOR
{
	unsigned int			max_index;
	unsigned int			max_free_index;
	unsigned int			current_free_index;
	CRITICAL_SECTION		*pcs;
	struct _MEMPOOL			*owner;
	struct _MEMNODE			*pfree[MAX_INDEX];
};

节点链表数组pfree

该结构中最最重要的那肯定就是pfree数组了,数组的每个元素都是_MEMNODE类型的地址,指向一个节点链表

pfree中的所有链表中的节点的大小并完全相同,这取决于当前链表在pfree数组中的索引,此处pfree数组的索引具有两层含义:1、该节点链表在数组中的实际索引;2、它同时还标记了当前链表中节点的大小(这就是上文提到的“索引大小”)。
所以,同一个链表中的所有节点大小都完全相等(索引0例外,后文会提)。而,索引越大,节点也就越大。
节点的大小与节点所在链表的索引存在如下关系:

节点大小=4K(index+1)*

因此,如果链表的索引为2,则该链表中所有的节点大小都是12K;如果索引为MAX_INDEX-1,即19,则节点大小应该为80K,这也是能够支持的“规则节点”的最大大小。

但是,对于索引0则不适用,当且仅当用户一次申请的内存块太大以至于超过了规则节点所能承受的80K的时候,才会到索引为0的链表中去查找合适的内存块。也就是说,索引为0的链表中的节点大小都大于80K,而且每个节点的大小都完全相同。

根据上面的描述,我们还是痛快的给出分配子的内存结构示意图吧:
分配子内存结构示意图

###分配子进行内存节点分配的步骤
分配子分配内存节点时其主要步骤如下:
1、按照前文所说的分配原则调整实际分配的空间大小。
2、根据实际大小转换成索引。
3、根据一定的规则,找到或新分配内存节点。规则如下:
: A、如果分配子中的“规则节点”能够满足需要分配的节点大小,找到能够满足分配的最小节点索引链表,但此时该索引对应的链表可能为空,因此将沿着数组往后查找直到找到第一个可用的不为空节点或者直到数组末尾。(还有部分细节参考相关代码)

  • 如果找到一个非空的链表,进入链表内部进行实际的内存分配。
  • 如果直到数组末尾所有链表都是空的,则向系统申请实际大小的空间,并初始化节点的各个变量。
: B、如果分配的节点大小超过了“ 规则节点”中的最大节点,将考虑 索引0链表,通过遍历 索引0链表,找到第一个满足大小的不为空节点。
C、若以上都没有找到合适的节点,则向系统申请实际大小的空间。

节点分配相关代码:
相关宏定义:

#define MEM_ALIGN(size,boundary) (((size) + ((boundary)-1)) & ~((boundary)-1))
#define MEM_ALIGN_DEFAULT(size) MEM_ALIGN(size,8)
#define BOUNDARY_INDEX	12
#define MAX_INDEX		20

相关常量:

const unsigned int BOUNDARY_SIZE 	= 1 << BOUNDARY_INDEX;	//4096
const unsigned int MIN_ALLOC 		= 2 * BOUNDARY_SIZE;	//8192
const unsigned int SIZEOF_MEMNODE 	= MEM_ALIGN_DEFAULT(sizeof(MEMNODE));//MEMNODE大小

节点分配函数完整代码:

MEMNODE *Allocator_alloc(ALLOCATOR *allocator, size_t in_size)
{
    MEMNODE *node, **ref;
    unsigned int max_index;
    size_t size, i, index;

    //4096的倍数,不满8192,则分配8192
    size = MEM_ALIGN(in_size + SIZEOF_MEMNODE, BOUNDARY_SIZE);
    if (size < in_size)	return NULL;
    if (size < MIN_ALLOC)	size = MIN_ALLOC;

    //将大小转为下标
    index = (size >> BOUNDARY_INDEX) - 1;

    //小于当前最大节点
    if (index <= allocator->max_index) 
	{
        if (allocator->pcs)
            EnterCriticalSection(allocator->pcs);

        //找到第一个可用的不为空节点
        max_index = allocator->max_index;
        ref = &allocator->pfree[index];
        i = index;
        while (*ref == NULL && i < max_index) 
		{
           ref++;
           i++;
        }

		//找到一个非空的链表
        if ( (node = *ref) ) 
		{
            if ((*ref = node->next) == NULL && i >= max_index) 
			{//当前链表没有后续node,并且拿走的node属于当前最大索引节点,则调整最大索引节点
                do 
				{
                    ref--;
                    max_index--;
                }
                while (*ref == NULL && max_index > 0);

                allocator->max_index = max_index;
            }

            allocator->current_free_index += node->index + 1;
            if (allocator->current_free_index > allocator->max_free_index)
                allocator->current_free_index = allocator->max_free_index;

            if (allocator->pcs)
                LeaveCriticalSection(allocator->pcs);

            node->next = NULL;
            node->first_avail = (char *)node + SIZEOF_MEMNODE;

            return node;
        }

		if (allocator->pcs)
			LeaveCriticalSection(allocator->pcs);
    }
    //大于当前最大节点,则用0索引 如果它不为空
    else if (allocator->pfree[0]) 
	{
		if (allocator->pcs)
			EnterCriticalSection(allocator->pcs);

        //找到第一个满足大小的不为空节点
        ref = &allocator->pfree[0];
        while (  (node = *ref)  && index > node->index)
            ref = &node->next;

        if (node) 
		{
            *ref = node->next;

            allocator->current_free_index += node->index + 1;
            if (allocator->current_free_index > allocator->max_free_index)
                allocator->current_free_index = allocator->max_free_index;

			if (allocator->pcs)
				LeaveCriticalSection(allocator->pcs);


            node->next = NULL;
            node->first_avail = (char *)node + SIZEOF_MEMNODE;

            return node;
        }

		if (allocator->pcs)
			LeaveCriticalSection(allocator->pcs);

    }

    //什么都没找到,直接申请
    if ((node = (MEMNODE*)malloc(size)) == NULL)
        return NULL;

    node->next = NULL;
    node->index = index;
    node->first_avail = (char *)node + SIZEOF_MEMNODE;
    node->endp = (char *)node + size;

    return node;
}

这里稍稍解释一下max_indexmax_free_index以及current_free_index
max_index 表示当前分配子管理的内存节点中最大的规则节点索引。(pfree数组中所有元素初始时都为空,而max_index始终记录了所有有值的索引中最大的那个)

max_free_index 表示分配子最大可管理的内存,用户可设置,默认为0,表示无限大。

current_free_index 表示分配子剩余可管理的内存,初始值等于max_free_index的值。当用户释放节点时,该值随之相应减少;当用户申请节点时,该值随之相应增加。后文节点释放时还会再提。

分配子进行内存节点释放的步骤

正如前面所描述的,在分配内存节点的时候,首先尝试到现有的链表中去查找适合的空间,如果没有适合的内存区域的话,必须按照上述的分配原则进行实际的内存分配并使用。但是,分配步骤中大家可能会疑惑,在分配过程中,向系统新申请的空间,并没有马上与pfree数组关联,而这个步骤其实是在节点释放时才进行的。

分配子进行内存节点的释放并不是真正的将内存归还给系统,而是将其回收到分配链表池(pfree)中。所以,pfree数组是在不断释放节点的过程中被构建起来的

进行节点释放时,由于_MEMNODE 不仅仅可能是一个节点,而且可能是一个节点链表,因此需要完全释放该链表中的所有节点,对于每个节点,根据它的索引大小(即内存大小)采取不同的处理策略:
1、如果节点的大小超过了分配子剩余可管理的大小current_free_index,那么就不能将其简单的归还到索引链表中,而必须将其完全归还给操作系统。当然也可以将max_free_index设为0,来表示没有回收门槛:任何内存,不管它有多大,都不会将其归还给操作系统。
2、如果节点的大小 < MAX_INDEX,则意味着该节点属于“规则节点”的范围。因此可以将该节点返回到对应的“规则链表”中。比如需要释放的节点的索引大小为 index ,则该节点将挂接于pfree[index]链表中。(还有部分细节参考相关代码)
3、如果节点超过了“规则节点”的范围,但是并没有超出回收节点的范围,此时我们则可以将其置于“索引0”链表的首部中。

当上面的工作都完成后,整个节点的释放也就完毕了。事实上整个内存池中的内存就是通过上面的不断地释放而构建起来的。一旦构建了内存池,下一次的时候则可以直接去内存池中获取了。

相关代码:

void Allocator_free(ALLOCATOR *allocator, MEMNODE *node)
{
    MEMNODE *next, *freelist = NULL;
    unsigned int index, max_index;
    unsigned int max_free_index, current_free_index;

	if (allocator->pcs)
		EnterCriticalSection(allocator->pcs);

    max_index = allocator->max_index;
    max_free_index = allocator->max_free_index;
    current_free_index = allocator->current_free_index;

    do 
	{
        next = node->next;
        index = node->index;

        if (max_free_index != 0
            && index + 1 > current_free_index) 
		{//已经超过最大容量,归还给系统
            node->next = freelist;
            freelist = node;
        }
        else if (index < MAX_INDEX) 
		{//小于80K,属于规则节点(1-19) 
            if ((node->next = allocator->pfree[index]) == NULL
                && index > max_index) 
			{//属于当前最大节点,更新max_index。
                max_index = index;
            }
            allocator->pfree[index] = node;
            if (current_free_index >= (index + 1))
                current_free_index -= (index + 1);
            else
                current_free_index = 0;
        }
        else 
		{//大于80K,挂到0索引中
            node->next = allocator->pfree[0];
            allocator->pfree[0] = node;
            if (current_free_index >= (index + 1))
                current_free_index -= (index + 1);
            else
                current_free_index = 0;
        }
    } while ( (node = next)  );

    allocator->max_index = max_index;
    allocator->current_free_index = current_free_index;

	if (allocator->pcs)
		LeaveCriticalSection(allocator->pcs);


    while (freelist != NULL) 
	{
        node = freelist;
        freelist = node->next;
        free(node);
    }
}

内存池

好了,最后才是我们实际需要使用到,也是用户直接接触的数据结构_MEMPOOL内存池。它负责组织内存的层次结构关系。其定义如下:

struct _MEMPOOL 
{
	struct _MEMPOOL			*parent;
	struct _MEMPOOL			*child;
	struct _MEMPOOL			*sibling;
	struct _MEMPOOL			**ref;

	struct _CLEANUP			*cleanups;
	struct _CLEANUP			*free_cleanups;

	struct _ALLOCATOR		*allocator;
	struct _MEMNODE			*active;
};

从前三个成员可以看出,_MEMPOOL之间形成树型层次结构。整个内存池层次树通过parentchild以及sibling三个变量构建起来。
parent指向当前内存池的父内存池;child指向当前内存池的子内存池;而sibling则指向当前内存池的兄弟内存池。其层次结构图,我就不再画了。

###活动节点active
当中需要着重介绍的成员,那当然就是active成员了,二话不说,先上内存结构示意图:
内存池结构示意图

Apache的内存池方案中,用户所有内存的使用通过内存池来分配,而其内部则通过分配子(allocator成员)分配内存节点,通过active成员管理池中内存链表,上面介绍_MEMNODE时提到的nextref正是跟active一起管理了一个完整的内存链表。下面的流程和代码相信能够帮助大家快速理解Apache的内存池的设计方案。

###用户申请内存的流程如下:
1 、使用现有内存池,或者创建新的内存池。创建内存池其实是通过分配子allocator分配节点_MEMNODE并将**_MEMPOOL紧跟_MEMNODE之后**,内存池初始状态如上图左边部分。
2 、从池中申请内存,根据申请大小判断active节点可用空间是否足够。
: A、若足够,则直接使用,并移动first_avail指针。返回其地址后,流程结束
: B、若不足,则继续进行3 之后的步骤

3、判断下一个内存节点的剩余空间是否足够,若足够则使用之,并将之脱离当前链表;若不足,则通过分配子分配新的内存节点(按照上文分配子分配内存节点所述)。
4、将第 3 步中得到的节点插入 active节点之前,并成为新的 active 节点。
5、计算旧的 active 节点的剩余空间大小,并且与其链表后的所有节点的剩余空间大小比较,并插入链表中正确的位置(链表中节点的剩余空间大小应从大到小排列)。

###内存的释放
关于内存的释放,可能是Apache内存池方案中一个我认为不能算做是缺点的缺点吧。
阅读到这里,如果你完全理解了上述所有内容,那么你肯定会发现,用户好像不能直接释放申请到的内存。
那么很遗憾的恭喜你,你没有错!确实如此,用户如果想要释放某次申请到的内存,必须释放该内存所在的整个内存池,这意味着,所有在该内存池及其所有子池中申请的内存都会被释放!

其实正是因为这一点,所以Apache才会引入父子池的概念,来使得不同内存池拥有不同的生命周期。

即:当某一些具有共同存在意义的内存其意义消失的时候一起释放。

比如,对于HTTP连接而言,包括两种内存池:连接内存池和请求内存池。由于一个连接可能包含多个请求,因此连接的生存周期总是比一个请求的周期长,为此连接处理中所需要的内存则从连接内存池中分配,而请求则从请求内存池中分配。而一个请求处理完毕后请求内存池被释放,一个连接处理后连接内存池被释放。

所以,当我们以逻辑块来区分内存,以具体业务来使得原本意义各不相同的内存块进行分门别类,区分出生命周期。那么,个人认为,一次性释放“相同”的内存(即释放整个内存池)反而是理所当然的,是极其美妙的。
至于我为什么会说这一点呢?因为我看到网上很多人说Apache内存池的缺点时都会提到这个,而我个人认为这并不能算做缺点,而是它的设计理念。

###相关代码
相关宏定义:

#define MEM_ALIGN(size,boundary) (((size) + ((boundary)-1)) & ~((boundary)-1))
#define MEM_ALIGN_DEFAULT(size) MEM_ALIGN(size,8)
#define BOUNDARY_INDEX	12
#define MAX_INDEX		20

#define node_free_space(node_) ((size_t)(node_->endp - node_->first_avail))

#define list_remove(node) do {		\
	*node->ref = node->next;		\
	node->next->ref = node->ref;	\
} while (0)

#define list_insert(node, point) do {           \
	node->ref = point->ref;                     \
	*node->ref = node;                          \
	node->next = point;                         \
	point->ref = &node->next;                   \
} while (0)

相关常量:

const unsigned int BOUNDARY_SIZE 	= 1 << BOUNDARY_INDEX;	//4096
const unsigned int MIN_ALLOC 		= 2 * BOUNDARY_SIZE;	//8192
const unsigned int SIZEOF_MEMNODE 	= MEM_ALIGN_DEFAULT(sizeof(MEMNODE));//MEMNODE大小
const unsigned int SIZEOF_MEMPOOL 	= MEM_ALIGN_DEFAULT(sizeof(MEMPOOL));//MEMPOOL大小

内存池创建函数:

BOOL Pool_Create(MEMPOOL **newpool,MEMPOOL *parent,ALLOCATOR *allocator /* = NULL */)
{
    MEMPOOL *pool;
    MEMNODE *node;

    if (newpool==NULL)
		return FALSE;

	if (parent == NULL)
		parent = global_pool;

    if (allocator == NULL)
        allocator = parent->allocator;


    if ((node = Allocator_alloc(allocator,MIN_ALLOC - SIZEOF_MEMNODE)) == NULL) 
        return FALSE;

    node->next = node;
    node->ref = &node->next;
	
	//正如步骤1中所说,将MEMPOOL紧跟MEMNODE之后
    pool = (MEMPOOL *)node->first_avail;
    node->first_avail = (char *)pool + SIZEOF_MEMPOOL;

    pool->allocator		= allocator;
    pool->active		= node;
    pool->child			= NULL;
    pool->cleanups		= NULL;
    pool->free_cleanups = NULL;

	//以下代码则是在构建MEMPOOL之间的树型结构
    if (  (pool->parent = parent) ) 
	{
        CRITICAL_SECTION *pcs;

        if ( (pcs = Allocator_CS_Get(parent->allocator)) )
            EnterCriticalSection(pcs);


        if ( (pool->sibling = parent->child) )
            pool->sibling->ref = &pool->sibling;

        parent->child = pool;
        pool->ref = &parent->child;


        if (pcs)
            LeaveCriticalSection(pcs);

    }
    else 
	{
        pool->sibling = NULL;
        pool->ref = NULL;
    }

    *newpool = pool;

    return TRUE;
}

最后就是大家最关心的实际内存申请的函数:

void * Mem_Alloc(MEMPOOL *pool, size_t in_size)
{
	MEMNODE *active, *node;
	void *mem;
	size_t size, free_index;

	size = MEM_ALIGN_DEFAULT(in_size);		//最接近的不小于8的倍数
	if (size < in_size) return NULL;

	active = pool->active;

	//如果有剩余空间,直接使用
	if (size <= node_free_space(active)) 
	{
		mem = active->first_avail;
		active->first_avail += size;

		memset(mem,0,in_size);
		return mem;
	}

	node = active->next;//查看下一个剩余空间是否够用
	if (size <= node_free_space(node)) 
	{
		list_remove(node);
	}
	else 
	{//还是不够用则后面的肯定也不够用,因为剩余空间大小是排过序的
		if ((node = Allocator_alloc(pool->allocator, size)) == NULL) 
			return NULL;
	}

	node->free_index = 0;	

	mem = node->first_avail;
	node->first_avail += size;

	list_insert(node, active);

	pool->active = node;

	free_index = (MEM_ALIGN(active->endp - active->first_avail + 1,
		BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;	//最近的小于4096的倍数

	active->free_index = free_index;
	node = active->next;
	if (free_index >= node->free_index)
	{
		memset(mem,0,in_size);
		return mem;
	}

	//按剩余空间从大到小排序
	do 
	{
		node = node->next;
	}
	while (free_index < node->free_index);

	list_remove(active);
	list_insert(active, node);

	memset(mem,0,in_size);
	return mem;
}

#其他
上文其实已经把Apache内存池中,最重要的部分都讲完了,剩余的都是些初始化、反初始化以及辅助功能函数。另外,我自己重新写的代码其实已经是简化版本,有兴趣的可以去看Apache内存池部分的源码。

我在最后把剩余的边角料都贴上来,就不作过多解释了。
分配子的创建与销毁:

BOOL Allocator_Create(ALLOCATOR **allocator)
{
	ALLOCATOR *new_allocator;

	if (allocator == NULL)
		return FALSE;

	if ((new_allocator = (ALLOCATOR*)malloc(SIZEOF_ALLOCATOR)) == NULL)
		return FALSE;

	memset(new_allocator, 0, SIZEOF_ALLOCATOR);

	*allocator = new_allocator;

	return TRUE;
}
void Allocator_Destroy(ALLOCATOR *allocator)
{
	unsigned int index;
	MEMNODE *node, **ref;

	for (index = 0; index < MAX_INDEX; index++) 
	{
		ref = &allocator->pfree[index];
		while ( (node = *ref) ) 
		{
			*ref = node->next;
			free(node);
		}
	}

	free(allocator);
}

分配子最大可管理内存设置:

void Allocator_max_free_Set(ALLOCATOR *allocator,size_t in_size)
{
	unsigned int max_free_index;
	unsigned int size = in_size;

	CRITICAL_SECTION *pcs;

	pcs = Allocator_CS_Get(allocator);
	if (pcs)
		EnterCriticalSection(pcs);


	max_free_index = MEM_ALIGN(size, BOUNDARY_SIZE) >> BOUNDARY_INDEX;
	allocator->current_free_index += max_free_index;
	allocator->current_free_index -= allocator->max_free_index;
	allocator->max_free_index = max_free_index;
	if (allocator->current_free_index > max_free_index)
		allocator->current_free_index = max_free_index;


	if (pcs)
		LeaveCriticalSection(pcs);
}

清理函数的注册与回调:(内存池销毁时,用户注册的自定义清理函数会被调用)

void Pool_Cleanup_Register(MEMPOOL *p, const void *data,CLEANUP_FUNC cleanup_fnc)
{
	CLEANUP *c;

	if ( p ) 
	{
		c =(CLEANUP *) Mem_Alloc(p, sizeof(CLEANUP));
		
		c->data = data;
		c->cleanup_func = cleanup_fnc;
		
		c->next = p->cleanups;
		p->cleanups = c;
	}
}
static void Run_Cleanups(CLEANUP **cref)
{
	CLEANUP *c = *cref;

	while (c) 
	{
		*cref = c->next;
		c->cleanup_func((void*)c->data);
		c = *cref;
	}
}

内部同步资源的创建与销毁:

BOOL CriticalSection_Create(CRITICAL_SECTION **ppCS,MEMPOOL *pool)
{
    (*ppCS) = (CRITICAL_SECTION *)Mem_Alloc(pool, sizeof(**ppCS));

	InitializeCriticalSection(*ppCS);

    Pool_Cleanup_Register(pool, (*ppCS), thread_mutex_cleanup);
    return TRUE;
}
static int thread_mutex_cleanup(void *data)
{
	CRITICAL_SECTION *pcs = (CRITICAL_SECTION *)data;

	DeleteCriticalSection(pcs);

	return true;
}

内存池释放:

void Pool_Destroy(MEMPOOL *pool)
{
    MEMNODE *active;
    ALLOCATOR *allocator;

    while (pool->child)
        Pool_Destroy(pool->child);

    Run_Cleanups(&pool->cleanups);

    if (pool->parent) 
	{

        CRITICAL_SECTION *pcs;

        if ( (pcs = Allocator_CS_Get(pool->parent->allocator)) )
            EnterCriticalSection(pcs);


        if ( (*pool->ref = pool->sibling) )
            pool->sibling->ref = pool->ref;

        if (pcs)
            LeaveCriticalSection(pcs);

    }

    
    allocator = pool->allocator;
    active = pool->active;
    *active->ref = NULL;


    if (Allocator_owner_Get(allocator) == pool)
	{ 
        Allocator_CS_Set(allocator, NULL);
    }

    Allocator_free(allocator, active);

    
    if (Allocator_owner_Get(allocator) == pool) 
	{
        Allocator_Destroy(allocator);
    }
}

最后是初始化与反初始化:
其内部建立了global_allocator与 global_pool,作为全局根池(是用户创建的所有内存池的父池),用户不可见,当用户调用反初始化的时候,全局根池被销毁,那么所有的内存都会被归还给系统。

static int			pools_initialized	= 0;
static ALLOCATOR	*global_allocator	= NULL;
static MEMPOOL		*global_pool		= NULL;

BOOL Pool_Initialize(void)
{
    if (pools_initialized++)
        return TRUE;

    if (Allocator_Create(&global_allocator) == FALSE) 
	{
        pools_initialized = 0;
        return FALSE;
    }

    if ( Pool_Create(&global_pool, NULL,global_allocator) == FALSE) 
	{
        Allocator_Destroy(global_allocator);
        global_allocator = NULL;
        pools_initialized = 0;
        return FALSE;
    }

	CRITICAL_SECTION* pcs;

	if ( CriticalSection_Create(&pcs,global_pool) == FALSE) 
	{
		return FALSE;
	}

	Allocator_CS_Set(global_allocator, pcs);

    Allocator_owner_Set(global_allocator, global_pool);

    return TRUE;
}

void Pool_Terminate(void)
{
    if (pools_initialized==0)
        return;

    if (--pools_initialized)
        return;

    Pool_Destroy(global_pool); /* This will also destroy the mutex */
    global_pool = NULL;

    global_allocator = NULL;
}

以上,就是全部了。
大家如果嫌Apache源码太庞大,而又不需要用到全部的功能,比如你不需要拥有多个allocator,不需要为内存池设置不同的allocator等更加复杂的功能。可以用我简化过的代码,这些代码我已经在项目中有运用检验过的了,完全没有问题。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值