Apache内存池内幕(5)

来源:互联网 发布:中国网络银行盈利模式 编辑:程序博客网 时间:2024/06/06 18:47
2.4.3内存池的创建
勿庸置疑,内存池的创建是内存池的核心操作之一。内存池创建函数的原型如下所示:
APR_DECLARE(apr_status_t) apr_pool_create_ex(apr_pool_t **newpool,
                                             apr_pool_t *parent,
                                             apr_abortfunc_t abort_fn,
                                             apr_allocator_t *allocator)
其中,newpool是需要创建的新的内存池,并且创建后的内存池通过该参数返回。parent则是当前创建的内存池的父亲;abort_fn指明了当创建失败的时候所调用的处理函数;allocator则是真正进行内存分配的分配子。
{
    apr_pool_t *pool;
    apr_memnode_t *node;
 
    *newpool = NULL;
    if (!parent)
        parent = global_pool;
    if (!abort_fn && parent)
        abort_fn = parent->abort_fn;
    if (allocator == NULL)
        allocator = parent->allocator;
在创建过程中,我们没有指定当前创建的内存池的父亲,则将其默认为父亲为根内存池global_pool,同时如果内存池关联的abort_fn和分配子allocator没有定义,那么也直接继承父辈的相关信息。
    if ((node = allocator_alloc(allocator,MIN_ALLOC - APR_MEMNODE_T_SIZE)) == NULL) {
        if (abort_fn)
            abort_fn(APR_ENOMEM);
        return APR_ENOMEM;
    }
    node->next = node;
    node->ref = &node->next;
    pool = (apr_pool_t *)node->first_avail;
    node->first_avail = pool->self_first_avail = (char *)pool + SIZEOF_POOL_T;
 
    pool->allocator = allocator;
    pool->active = pool->self = node;
    pool->abort_fn = abort_fn;
    pool->child = NULL;
    pool->cleanups = NULL;
    pool->free_cleanups = NULL;
    pool->subprocesses = NULL;
    pool->user_data = NULL;
    pool->tag = NULL;
在一切就绪之后,函数将必须首先创建apr_pool_t结构。但是前面我们曾经说过Apache中对所有内存的分配都是以内存结点apr_memnode_t进行分配的,而且每次分配的最小单元为8K,这对于创建apr_pool_t结构也不例外。因此函数将首先调用分配子allocator分配8K的内存,然后将最顶端的内存分配给apr_memnode_t结构。此时接着apr_memnode_t结构下面的内存才能继续分配给apr_pool_t,用来表示内存池结构,apr_pool_t结构之后才是真正可用的空间。在整个8K内存中,结点头和内存池头部分别占用的空间大小为APR_MEMNODE_T_SIZE和SIZEOF_POOL_T,因此用户真正可用的空间实际上只有(8k-APR_MEMNODE_T_SIZE-SIZEOF_POOL_T)大小了,至此我们还必须要调整apr_memnode_t中的first_avail指针和apr_pool_t结构中的self_first_avail指针指向真正可用空间。
经过两轮分配之后,8K内存的布局如图3.5所示:
 
一旦完成了内存池结点的分配工作,我们必须将其挂结到内存池层次树上。挂结的过程无非就是设置parent,child以及sibling的过程。
    if ((pool->parent = parent) != NULL) {
        if ((pool->sibling = parent->child) != NULL)
            pool->sibling->ref = &pool->sibling;
        parent->child = pool;
        pool->ref = &parent->child;
    }
    else {
        pool->sibling = NULL;
        pool->ref = NULL;
    }
    *newpool = pool;
挂结的过程可以分为下面几个步骤:
(1)、将当前的结点的parent指针指向父结点,即pool->parent = parent。
(2)、设定当前结点的sibing。sibing应该指向那些与当前结点处于同一层次,并且父结点也相同的结点,新的结点总是被插入到子结点链表的首部,插入通过下面的两句实现:
pool->sibling = parent->child;
parent->child = pool;
不过如果父结点为空,意味着该结点未有兄弟结点,故pool->sibling = NULL。
(3)、设置ref成员。在apr_pool_t中,ref用于指向
在内存池结点创建的过程中,我们可以看到,内存池创建后active仍然为空。因此当前内存池中能够被使用的内存仅仅为8k- APR_MEMNODE_T_SIZE-SIZEOF_POOL_T大小。如果用户从内存池中申请更多的内存的时候,很明显,此时必须通过active去扩展该内存池对应的内存结点。这一点我们可以在内存池的内存分配中看出来。
关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish at sina.com.cn与之联系!

如果你觉得本文不错,请点击文后的“推荐本文”链接!!