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&#

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值