欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[APR源码解析]内存池

程序员文章站 2022-04-14 13:29:46
...

说到apr的内存池,必须知道如何构建一个环形双向链表。因为apr内存池apr_pool_t的active就是一个环形双向链表。

 

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

这个micro的用处,是将point插到node的前面。 假设point前的节点为bpoint。

 

node->ref = point->ref=&bpoint->next。因此*node->ref=bpoint->next=node,这样就让bpoint的next指向了node.

经过node->next=point 后。就可以得到链表:bpoint->next=node,node->next=point。point->ref=&node->next,node->ref=&bpoint->next。所以很巧妙的是ref,他既能表示指向自己(用**ref获取),用能让上一个节点的next(用*ref获取)指向其他节点。插入时,用他来指向新节点。删除时,用他来指向下一个节点。下面的代码就实现这个。

 

 

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

 还有一种特殊情况:目前系统只有一个节点。即bpoint=point。此时就得模拟一个bpoint(bpoint=point)。

bpoint->next = point

point->ref = &bpoint->next

这个代码在apr_pool_create_ex就会出现。

node->next = node;
node->ref = &node->next;

 有了以上基础,内存池处理就简单了。

 

 1.apr_pool_initialize内存池初始化

主要作用是通过apr_pool_create_ex创建一个apr_pool_t,此pool放在一个node节点中。此node又放在了pool的active中,形成一个active环形双向链表的第一个节点。当然也同时实现了对bpoint的模拟。

 2.apr_palloc内存分配

 

APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t in_size)
{
    apr_memnode_t *active, *node;
    void *mem;
    apr_size_t size, free_index;

    size = APR_ALIGN_DEFAULT(in_size);
    if (size < in_size) {
        if (pool->abort_fn)
            pool->abort_fn(APR_ENOMEM);

        return NULL;
    }
    active = pool->active;

    /* If the active node has enough bytes left, use it. */
    if (size <= node_free_space(active)) {
        mem = active->first_avail;
        active->first_avail += size;

        return mem;
    }

    node = active->next;
    if (size <= node_free_space(node)) {
        list_remove(node);
    }
    else {
        if ((node = allocator_alloc(pool->allocator, size)) == NULL) {
            if (pool->abort_fn)
                pool->abort_fn(APR_ENOMEM);

            return NULL;
        }
    }

    node->free_index = 0;

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

    list_insert(node, active);

    pool->active = node;

    free_index = (APR_ALIGN(active->endp - active->first_avail + 1,
                            BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;

    active->free_index = (APR_UINT32_TRUNC_CAST)free_index;
    node = active->next;
    if (free_index >= node->free_index)
        return mem;

    do {
        node = node->next;
    }
    while (free_index < node->free_index);

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

    return mem;
}
分配的原则是,如果pool的active的第一节点(node1,为了方便,以下第二节点称为node2,第三为node3......)有足够内存则直接返回内存。mem指向内存的首地址。并将node的内存剩余空间扣去size。如果pool的node1不足,则选择node2,如果node2足够则使用node2(此时要将这个节点先拿出来list_remove(node);),否则就用分配子分配一个node。并将node插入到第一个节点中( list_insert(node, active);)。 此时的active就成为第二节点。

 

active = pool->active;

    /* If the active node has enough bytes left, use it. */
    if (size <= node_free_space(active)) {
        mem = active->first_avail;
        active->first_avail += size;

        return mem;
    }

    node = active->next;
    if (size <= node_free_space(node)) {
        list_remove(node);
    }
    else {
        if ((node = allocator_alloc(pool->allocator, size)) == NULL) {
            if (pool->abort_fn)
                pool->abort_fn(APR_ENOMEM);

            return NULL;
        }
    }

    node->free_index = 0;

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

    list_insert(node, active);

    pool->active = node;

 

 为了下次分配效率更高,此时需将active这个环形双向链表做一些调整。确保第二个节点为所有节点剩余空间最多的(即从第二节点开始按剩余空间的大小降序排列)。这样下一次分配空间时,就只要判断以上代码。

 

降序排列是怎么实现呢?其实只要将每次的node2节点放到适合他的位置就可以,上一次是降序,因为只有node2会改变大小。所以只要将node2放到合适位置去,从第二个节点开始后的链表还是降序。

那如何放node2呢?

第一,获取node2的free_index。

 

 

free_index = (APR_ALIGN(active->endp - active->first_avail + 1,
                            BOUNDARY_SIZE) - BOUNDARY_SIZE) >> BOUNDARY_INDEX;

    active->free_index = (APR_UINT32_TRUNC_CAST)free_index;
 第二,和一下节点(node3)的free_index比较,如果大于等于node3则,什么都不用变化。如果小于,则找一个合适的位置。

 

 

do {
        node = node->next;
    }
    while (free_index < node->free_index);
 如果node2这个节点的内存大小比任何一个节点的大小都要小,这样怎么办。应该把位置放哪里?这就是apr巧妙的地方,因为active是一个环形链表,所以最后个节点的下一个节点就是node1。代码中有一句 node->free_index = 0;(此时的node就是node1),这一句相信很多人都不知为什么要这样做。其实就是要让以上的循环永远可以终止。因为node2的剩余大小肯定大于0,所以node2这个节点的内存大小比任何一个节点的大小都要小时,其实他会大于第一个节点。这样他就会被插在第一个节点的前面,也就是成为最后一个节点

 

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

 这个代码实现将active(每二个节点)插入到合适它的位置。

 

 

 

相关标签: 内存池 apr