文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>Apache中内存池实现技术(2)

Apache中内存池实现技术(2)

时间:2005-12-19  来源:tingya

本系列文章详细分析了Apache中内存池的实现技术,本文是该系列的第2篇文章

2.3 内存池分配子allocator

2.3.1分配子概述

尽管我们可以通过malloc函数直接分配apr_memnode_t类型的结点,不过Apache中并不推荐这种做法。事实上Apache中的大部分的内存的分配都是由内存分配子allocator完成的。它隐藏了内部的实际的分配细节,对外提供了几个简单的接口供内存池函数调用。内存分配子属于内部数据结构,外部程序不能直接调用。内存分配子(以后简称为分配子)在文件apr_pools.c中进行定义如下:

struct apr_allocator_t {

      apr_uint32_t        max_index;

      apr_uint32_t        max_free_index;

      apr_uint32_t        current_free_index;

#if APR_HAS_THREADS

      apr_thread_mutex_t  *mutex;

#endif /* APR_HAS_THREADS */

      apr_pool_t          *owner;

      apr_memnode_t     *free[MAX_INDEX];

};

该结构中最重要的无非就是free数组,数组的每个元素都是apr_memnode_t类型的地址,指向一个apr_memnode_t类型的结点链表。内存分配的时候则从实际的结点中进行分配,使用完毕后同时返回给分配子。

不过free中的链表中结点的大小并不完全相同,其取决于当前链表在free数组中的索引。此处free数组的索引index具有两层次的含义:第一层,该结点链表在数组中的实际索引,这是最表层的含义;另外,它还标记了当前链表中结点的大小。索引越大,结点也就越大。同一个链表中的所有结点大小都完全相等,结点的大小与结点所在链表的索引存在如下的关系:

结点大小 =  8K + 4K*(index-1)

因此如果链表索引为2,则该链表中所有的结点大小都是12K;如果索引为MAX_INDEX,即20,则结点大小应该为8K+4K*(MAX_INDEX-1)=84K,这也是Apache中能够支持的“规则结点”的最大数目。不过这个公式仅仅适用于数组中1到MAX_INDEX的索引,对于索引0则不适合。当且仅当用户申请的内存块太大以至于超过了规则结点所能承受的84K的时候,它才会到索引为0的链表中去查找。该链表中的结点通常都大于84K,而且每个结点的大小也不完全相同。

在后面的部分,我们将索引1到MAX_INDEX所对应的链表统称为“规则链表”,而每一个链表则分开称之为“索引n链表”,与之对应,规则链表中的结点则统称为“规则结点”,或者称则为“索引n结点”,这是因为它们的大小有一定的规律可遵循;而索引0对应的链表则称之为“索引0链表”,结点则称之为“索引0结点”。

根据上面的描述,我们可以给出分配子的内存结构如图3.2所示。

图3.2 分配子内存结构示意

理论上,分配子中的最大的结点大小应该为8K+4K*(MAX_INDEX-1),但实际却未必如此,如果从来没有分配过8K+4K*(MAX_INDEX-1)大小的内存,那么MAX_INDEX索引对应的链表很可能是空的。此时在分配子中我们用变量max_index表示实际的最大结点。另外如果结点过大,则占用内存过多,此时有必要将该结点返回给操作系统,分配子将max_free_index作为内存回收的最低门槛。如果该结点小于max_free_index,则不做任何处理,否则使用后必须进行释放给操作系统。current_free_index则是…。除此之外,mutex用户保证多线程访问时候的互斥,而owner则记录了当前分配子所属于的内存池。

针对分配子,Apache中提供了几个相关的函数,函数名称和作用简要概述如表格3.1。

表3.1 Apache中提供了分配子相关函数

分配子操作

函数名称

函数功能简单描述

创建

apr_allocator_create

创建一个新的分配子

销毁

apr_allocator_destroy

销毁一个已经存在的分配子

空间分配

apr_allocator_alloc

调用分配子分配一定的空间

空间释放

apr_allocator_free

释放分配子已经分配的空间,将它返回给分配子

其余设置

apr_allocator_owner_set

apr_allocator_owner_get

设置和获取分配子所属的内存池

apr_allocator_max_free_set

apr_allocator_set_max_free

设置和获取分配子内部的互斥变量

2.3.2分配子创建与销毁

分配子的创建是所有的分配子操作的前提,正所谓“毛之不存,皮将焉附”。分配子创建使用函数apr_allocator_create实现:

APR_DECLARE(apr_status_t) apr_allocator_create(apr_allocator_t **allocator)

{

    apr_allocator_t *new_allocator;

    *allocator = NULL;

    if ((new_allocator = malloc(SIZEOF_ALLOCATOR_T)) == NULL)

        return APR_ENOMEM;

    memset(new_allocator, 0, SIZEOF_ALLOCATOR_T);

    new_allocator->max_free_index = APR_ALLOCATOR_MAX_FREE_UNLIMITED;

    *allocator = new_allocator;

    return APR_SUCCESS;

}

分配子的创建非常的简单,它使用的函数则是最通常的malloc,分配大小为SIZEOF_ALLOCATOR_T即APR_ALIGN_DEFAULT(sizeof(apr_allocator_t))大小。当然这块分配的空间也包括了MAX_INDEX个指针变量数组。一旦分配完毕,函数将max_free_index初始化为APR_ALLOCATOR_MAX_FREE_UNLIMITED,该值实际为0,表明分配子对于回收空闲结点的大小并不设门槛,意味着即使结点再大,系统也不会回收。

创建后,结构中的max_inde,current_free_index都被初始化为0,这实际上是由memset函数隐式初始化的。一旦创建完毕,函数将返回创建的分配子。只不过此时返回的分配子中的free数组中不包含任何的实际的内存结点链表。

对分配子使用的正常的下一步就应该是对结构成员进行初始化。主要的初始化工作就是设置系统资源归还给操作系统的门槛max_free_index。在后面我们会看到,对于使用malloc分配的内存,如果其大小小于该门槛值,那么这些资源并不释放,而是归还给内存池,当内存池本身被释放的时候,这些内存才真正释放给操作系统;如果内存的大小大于这个门槛值,那么内存将直接释放给操作系统。这个门槛值的设置由函数apr_allocator_max_free_set完成:

APR_DECLARE(void) apr_allocator_max_free_set(apr_allocator_t *allocator,

                                             apr_size_t in_size)

{

    apr_uint32_t max_free_index;

    apr_uint32_t size = (APR_UINT32_TRUNC_CAST)in_size;

    max_free_index = APR_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;

}

参数中的size经过适当的对齐调整赋值给分配子结构中的max_free_index。除了max_free_index之外,另外一个重要的成员就是current_free_index,该成员记录当前内存池中实际的最大的内存块大小。当然,它的值不允许超出max_free_index的范围。

与分配子的创建对应的则是分配子的销毁,销毁使用的是函数apr_allocator_destroy。当分配子被销毁的时候,我们需要确保下面两方面的内容都被正确的销毁:

(1)、分配子本身的内存被释放,这个可以直接调用free处理

(2)、由于分配子中内嵌的free数组都指向一个实际的结点链表,因此必须保证这些链表都被正确的释放。在释放链表的时候,通过一旦得到头结点,就可以沿着next遍历释放链表中的所有结点。

必须需要注意的是两种释放之前的释放顺序问题。正确的释放顺序应该是链表释放最早;其次才是分配子本身内存的释放。Apache中对应该部分是释放代码如下:

APR_DECLARE(void) apr_allocator_destroy(apr_allocator_t *allocator)

{

    apr_uint32_t index;

    apr_memnode_t *node, **ref;

    for (index = 0; index < MAX_INDEX; index++) {

        ref = &allocator->free[index];

        while ((node = *ref) != NULL) {

            *ref = node->next;

            free(node);

        }

    }

    free(allocator);

}

////////////////////////////////////////////////////////////////////////
转载须知:
本文作者:张中庆,网名,flydish
最早出处:http://blog.csdn.net/tingya
作者联系地址:flydish1234#sina.com.cn,请把其中的#替换为@,防止Spam收集
请保留上述信息,欢迎共同探讨关于Apache源代码的一切
关键词:Apache,Memory Pool,内存池, APR
//////////////////////////////////////////////////////////////////////

 

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载