APR内存池概述 下载本文

目录

1. 内存池概述 2. 内存池分配结点 3. 内存池分配子ALLOCATOR 3.1. 3.2. 3.3. 3.4. 3.5.

分配子概述

分配子创建与销毁 分配子内存分配 分配子内存释放

分配子内存管理流程

2 3 6 6 8 10 13 15 16 16 19 20 23 24 24 24 24 25 27 27

4. 内存池

4.1. 内存池概述

4.2. 内存池的初始化

5. APACHE内存池中分配结点补充说明

6. 内存池中CURRENT_FREE_INDEX的作用的解释 7. 内存分配子的CURRENT_FREE_INDEX成员作用 7.1. 7.2. 7.3. 7.4. 7.5.

背景 历史

CURRENT_FREE_INDEX与MAX_FREE_INDEX的作用

关于CURRENT_FREE_INDEX溢出的问题 溢出问题的解决

8. 简单应用

Apache内存池内幕

对于APR中的所有的对象中,内存池对象应该是其余对象内存分配的基础,不仅是APR中的对象,而且对于整个Apache中的大部分对象的内存都是从内存池中进行分配的,因此我们将把内存池作为整个APR的基础。 1. 内存池概述

在 C语言中,内存管理的问题臭名昭著,一直是开发人员最头疼的问题。对于小型程序而言,少许的内存问题,比如内存泄露可能还能忍受,但是对于Apache这 种大负载量的服务器而言,内存的问题变得尤其重要,因为丝毫的内存泄露以及频繁的内存分配都可能导致服务器的效率下降甚至崩溃。

通常情况下,内存的分配和释放通常都是mallloc和free显式进行的。这样做显得单调无味,同时也可能充满各种令人厌恶的问题。对同一块内存的多次释放通常会导致页面错误,而一直不释放又导致内存泄露,并且使得服务器性能大大下降。

为了在大而且复杂的Apache中避免内在的内存管理问题,Apache的开发者创建了一套基于池概念的内存管理方案,最后这套方法移到APR中成为通用的内存管理方案。

在这套方案中,核心概念是池的概念。Apache中的内存分配的基本结构都是资源池,包括线程池,套接字池等等。内存池通常是一块很大的内存空间,一次性被分配成功,然后需要的时候直接去池中取,而不需要重新分配,这样避免的频繁的malloc操作,而且另一方面,即时内存的使用者忘记释放内存或者根本就不想分配,那么这些内存也不会丢失,它们仍然保存在内存池中,当内存池被销毁的时候这些内存将自动的被销毁。

由于Apache中的大部分资源的分配都是从内存池中分配的,因此对于大部分的Apache函数,如果其内部需要进行资源分配,那么它的函数参数中总是会带有一个内存池参数,该内存池参数指明分配内存来自的内存池,比如下面的两个函数:

APR_DECLARE(apr_array_header_t *) apr_array_copy(apr_pool_t *p,const apr_array_header_t *arr); APU_DECLARE_NONSTD(apr_status_t) apr_bucket_setaside_noop(apr_bucket *data,apr_pool_t *pool);

由于在函数的内部需要进行内存分配,因此这两个函数的参数中都指定了一个apr_pool_t的结构,用以指明函数内存分配来自的内存池。在后面的大部分过程中我们对于该参数将不再做多余的解释。

Apache 中的内存池并不是仅仅一个内存池,相反而是存在多个内存池,这些内存池之间形成层次结构。如果Apache中仅仅存在一个内存池的话,潜在的问题是所有的内存分配都来自这个池,而且最要命的这些内存必须在整个Apache关闭时候才被释放,这一点显然不是那么合情合理,为此Apache中根据处理阶段的周期长短又引出了子内存池的概念,与之对应的

是父内存池以及根内存池的概念,它们的唯一区别就是存在的周期的不同而已。比如对于HTTP连接而言,包括两种内存池:连接内存池和请求内存池。由于一个连接可能包含多个请求,因此连接的生存周期总是比一个请求的周期长,为此连接处理中所需要的内存则从连接内存池中分配,而请求则从请求内存池中分配。而一个请求处理完毕后请求内存池被释放,一个连接处理后连接内存池被释放。根内存池在整个Apache运行期间都存在。Apache中一个内存池的层次结构图可以大致如下描述:

内存池层次图

2. 内存池分配结点

在了解内存池的概念之前,我们首先了解一些内存池分配结点的概念。为了能够方便的对分配的内存进行管理,Apache中使用了内存结点的概念来描述每次分配的内存块。其结构类型则描述为apr_memnode_t,该结构定义在文件 apr_allocator.h中,其定义如下: /** basic memory node structure */ struct apr_memnode_t {

apr_memnode_t *next; /**< next memnode */ apr_memnode_t **ref; /**< reference to self */ apr_uint32_t index; /**< size */

apr_uint32_t free_index; /**< how much free */

char *first_avail; /**< pointer to first free memory */ char *endp; /**< pointer to end of free memory */ };

该结点类型是整个Apache内存管理的基石,在后面的部分我们将其称之为“内存结点类型”或者简称为“内存结点”或者“结点”。在该结构中,不同的结点之间通过next指针形成结点链表;另外当在结点内部的时候为了方便引用结点本身,成员变量中还引入了ref,该变量主

要用来记录当前结点的首地址,即使身在结点内部,也可以通过ref指针得到该结点并对该结点进行操作。

从上面的结构中可以看出事实上在apr_memnode_t结构内部没有任何的“空闲空间”来容纳实际分配的内存,事实上,它从来不单独存在,总是依附于具体的分配的内存单元。通常情况下,一旦分配了实际的空间之后,Apache总是将该结构置于整个单元的最顶部,如下图所示。

内存结点示意图

在上图中,我们可能调用malloc函数分配了16K大小的空间,为了能够将该空间用Apache的结点进行记录,我们将apr_memnode_t置于整个空间的头部,此时剩下的可用空间大小应该为16K-sizeof(apr_memnode_t),同时结构中还提供了 first_avail和end_p指针分别指向这块可用空间的首部和尾部。当这块可用空间被不断利用时,first_avail和end_p指针也不断随之移动,不过(end_p-first_avail)之间则永远是当前的空闲空间。上图的右边部分演示了这种布局。

通常情况下,其分配语句大致如下:

apr_memnode_t* node;

node = (apr_memnode_t*)malloc(size); node->next = NULL; node->index = index;

node->first_avail = (char *)node + APR_MEMNODE_T_SIZE; node->endp = (char *)node + size;

Apache中对内存的分配大小并不是随意的,随意的分配可能会造成更多的内存碎片。为此Apache采取的则是“规则块”分配原则。Apache所支持的分配的最小空间是8K,如果分配的空间达不到8K的大小,则按照8K去分配;如果需要的空间超过8K,则将分配的空间往上