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);
}

关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish at sina.com.cn与之联系!

如果你觉得本文不错,请点击文后的“推荐本文”链接!!
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页