1、Apache内存池内幕(1)对于APR中的所有的对象中,内存池对象应该是其余对象内存分配的基础,不仅是APR中的对象,而且对于整个Apache中的大部分对象的内存都是从内存池中进行分配的,因此我们将把内存池作为整个APR的基础。2.1 内存池概述在C语言中,内存管理的问题臭名昭著,一直是开发人员最头疼的问题。对于小型程序而言,少许的内存问题,比如内存泄露可能还能忍受,但是对于 Apache这种大负载量的服务器而言,内存的问题变得尤其重要,因为丝毫的内存泄露以及频繁的内存分配都可能导致服务器的效率下降甚至崩溃。通常情况下,内存的分配和 通常都是 a c和 ee 进行的。这 得 , 可能 种 人
2、的问题。对 一 内存的 通常 导致 ,而一直不 导致内存泄露,且得服务器能大大下降。为在大而且的Apache中currency1内在的内存管理问题,Apache的开发“一基于池概的内存管理fifl,最 这fiAPR中为通 的内存管理fifl。在这fifl中,概是池的概。Apache中的内存分配的基”都是池, 程池, 池。内存池通常是一 大的内存 ,一 分配, 要的 直 池中 ,而不 要重分配,这 currency1的频繁的 a c作,而且一fi , 内存的 内存 不 分配, 这 内存 不 , 们 存在内存池中, 内存池 的 这 内存将 的 。于Apache中的大部分的分配都是从内存池中分配的,
3、因此对于大部分的Apache ,如 其内部 要进行分配, 的 中是 有一个内存池 ,该内存池 分配内存 的内存池,比如下 的 个APR C AR ap a a hea e ap a a c p ap p pc ap a a hea e a AP C AR O ST ap a u ap bucke e a i e p ap bucke a aap p p 于在 的内部 要进行内存分配,因此这 个 的 中都定一个ap p 的”, 以名 内存分配 的内存池。在 的大部分过程中我们对于该 将不再 余的解 。Apache中的内存池不是仅仅一个内存池,相反而是存在 个内存池,这 内存池之形层 ”。如 Ap
4、ache中仅仅存在一个内存池的 话,潜在的问题是所有的内存分配都 这个池,而且最要命的这 内存必须在整个Apache关闭 才 ,这一点 不是 合情合理,为此 Apache中 据处理阶段的周期长短 引出子内存池的概,与之对应的是父内存池以及 内存池的概, 们的唯一区别 是存在的周期的不 而已。比如 对于HTTP连 而言, 种内存池 连 内存池和请求内存池。 于一个连 可能含 个请求,因此连 的生存周期是比一个请求的周期长,为此连 处 理中所 要的内存则从连 内存池中分配,而请求则从请求内存池中分配。而一个请求处理完毕 请求内存池 ,一个连 处理 连 内存池 。 内存池 在整个Apache运行期都
5、存在。Apache中一个内存池的层 ”图可以大致如下描述 内存池的层 图2.2 内存池分配点在解内存池的概之前,我们首先解一 内存池分配点的概。为能够fi便的对分配的内存进行管理,Apache中 内存点的概 描述每 分配的内存 。其”类型则描述为ap e e ,该”定义在文件Ap a ca .h中,其定义如下 / ba ic e e uc u e /uc ap e e ap e e ex /max_free_index = APR_ALLOCATOR_MAX_FREE_UNLIMITED;*allocator = new_allocator;return APR_SUCCESS;分配子的“常的
6、简 , 的 则是最通常的 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 中不含任何的实
7、际的内存点链表。对分配子 的常的下一步 应该是对”员进行初始化。主要的初始化工作 是设置归还作 的 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_
8、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 m
9、ax_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。 分配子 的 ,我们 要fl 下 fi 的内容都fl的 (1)、分配子身的内存 ,这个可以直 free处理(2)、 于分配子中
10、内嵌的free 都向一个实际的点链表,因此必须 这 链表都fl的 。在 链表的 ,通过一旦得头点, 可以沿着next遍历 链表中的所有点。必须 要注意的是 种 之前的 顺序问题。fl的 顺序应该是链表 最早;其才是分配子身内存的 。Apache中对应该部分是 如下 APR_DECLARE(void) apr_allocator_destroy(apr_allocator_t *allocator)apr_uint32_t index;apr_memnode_t *node, *ref;for (index = 0; index freeindex;while (node = *ref) !=
11、NULL) *ref = node-next;free(node);free(allocator);Apache内存池内幕(3)2.3.3分配子内存分配 分配子分配内存是最终的 的。Apache对外提供的 分配子分配内存的 是apr_allocator_alloc,而实际在内部,该 的则是allocator_alloc。allocator_alloc 型声 如下 apr_memnode_t *allocator_alloc(apr_allocator_t *allocator, apr_size_t size)的 常简 ,allocator则是内存分配的 的分配子,而size则是 要进行分配的
12、大小。如 分配,则 分配 的apr_memnode_t”。apr_memnode_t *node, *ref;apr_uint32_t max_index;apr_size_t i, index;size = APR_ALIGN(size + APR_MEMNODE_T_SIZE, BOUNDARY_SIZE);if (size BOUNDARY_INDEX) - 1;if (index APR_UINT32_MAX) return NULL;所 的一件事情 是 我们前 所的分配 则 整实际分配的大小 如 不 8K,则以8K; 则 整为4K的整 。 还将该与该点对应的引大小。一旦得 引大小,
13、点链表。至此Apache可以寻 合 的点进行内存分配。从分配子中分配内存必须考虑下 三种情况 (1)、如 要分配的点大小分配子中的“ 则点”能够 足, indexmax_index。此 ,能够 足分配的最小点 是index 引对应的链表点,但此 该 引对 应的链表可能为,因此 将沿着 直 一个可 的不为点直 末尾。 程序中还出外一种 以及不 的 因 NOTE: an optimization would be to check allocator-freeindex first and if no node is present, directly use allocator-freemax_
14、index. This seems like overkill though and could cause memory waste.外一种fifl 是首先直 检 allocator-freeindex,一旦发现不可 ,直 最大的引 allocator-freemax_index,不过这种 可能导致内存的浪费。Apache 的则是“最合 ” 则, 这种 则, 的一个内存肯定是最合 的。下 的斜体所作的 如此 if (index max_index) max_index = allocator-max_index;ref = i = index;while (*ref = NULL max_i
15、ndex-;while (*ref = NULL allocator-max_index = max_index;allocator-current_free_index += node-index;if (allocator-current_free_index allocator-max_free_index)allocator-current_free_index = allocator-max_free_index;node-next = NULL;node-first_avail = (char *)node + APR_MEMNODE_T_SIZE;return node;(2)、
16、如 分配的点大小 过 “ 则点”中的最大点, 将考虑 引0链表。 引0链表中的点的实际大小通过员变量index进行 。在通过next遍历 引0链表的 , 将 要的大小index和实际的点的大小node-index进行比较。如 indexnode-index,则 该点 足分配要求,此 必须继续遍历。一旦 合 的可供分配的点大小, 将 整 node-first_avail针向实际可 的闲。外还 要 整分配子中的current_free_index为的分配 的 ”。(3)、如 在free0链表中都 不合 的供分配, 此 能“起炉灶”。 能 的事情 是 malloc分配实际大小的,初始化点的 个变量,
17、 ,如下 if (node = malloc(size) = NULL)return NULL;node-next = NULL;node-index = index;node-first_avail = (char *)node + APR_MEMNODE_T_SIZE;node-endp = (char *)node + size;下 我们 看一个Apache中典型的 分配子分配的情况,下 的你可以在worker.c中 apr_allocator_t *allocator;apr_allocator_create(apr_allocator_max_free_set(allocator,
18、ap_max_mem_free);apr_pool_create_ex(apr_allocator_owner_set(allocator, ptrans);我顺着这段 下阅读的 ,我感觉困惑。 一个分配子“初始,内部的free 中的 引链表都为,因此 我们在 apr_pool_create_ex 中 node = allocator_alloc(allocator, MIN_ALLOC - APR_MEMNODE_T_SIZE) = NULL的 ,所 要的内存 不可能 引链表内的点中,而能 地分配,这 点一旦分配 , 们 作为内存池的点而 ,但是分配 的点 没有立与free 进行关联,没有对
19、free 中的元 进行”。这 ,如 不将点与free 进行“挂 ”, 将永远都不可能形 图一所示链表”。们 才挂 free 中的呢? 所有的挂 过程都是在点 的 才进行的。2.3.4分配子内存 如前 所描述的,在分配内存的 ,Apache首先尝试现有的链表中 合的,如 没有 合的内存区的话,Apache必须 上述的分配 则进行实际的内存分配 。但是实际的内存 不 立挂 链表中,有 的 ,这 区才挂内存中。所以从这个角度而言, 分配子内存的 不是真的将内存 free ,而将其 分配链表池中。Apache中提供的内存 是apr_allocator_free。不过该 仅仅是对外提供的 而已,在 内存
20、 的则实际上是allocator_free。allocator_free 的 型如下 static APR_INLINE void allocator_free(apr_allocator_t *allocator, apr_memnode_t *node)中,node是 要 的内存点,其最终归还分配子allocator。apr_memnode_t *next, *freelist = NULL;apr_uint32_t index, max_index;apr_uint32_t max_free_index, current_free_index;max_index = allocator-
21、max_index;max_free_index = allocator-max_free_index;current_free_index = allocator-current_free_index;于node不仅仅可能是一个点,而且可能是一个点链表,因此如 要完 该链表中的点,则必须通过点中的next进行依 遍历,因此下 的环 是整个 过程的框架” do next = node-next;index = node-index; while (node = next) != NULL);对于每个点,我们将 据 的 引大小(内存大小) 不 的处理 (1)、如 点的大小 过完 的阙”max_f
22、ree_index, 我们 不能将其简 的归还 引链表中,而必须将其完归还 作 。 将所有的这 的 要完 的点 存在链表freelist中,待所有的点遍历完毕 , 要 freelist 可以 所有的必 须 的点,如下所示 if (max_free_index != APR_ALLOCATOR_MAX_FREE_UNLIMITEDfreelist = node;如 max_free_index为APR_ALLOCATOR_MAX_FREE_UNLIMITED则意 着没有 。任何内存,不管 有 大,APR都不 将其归还作 。(2)、如 indexmax_index,则必须重 max_index的大
23、小, 将 该点插入链表的首部,作为首点,可以描述如下 else if (index next = allocator-freeindex) = NULLallocator-freeindex = node;current_free_index -= index;(3)、如 点 过“ 则点”的范围,但是没有 出 点的范围,此 我们则可以将其置于“ 引0”链表的首部中。如下 else node-next = allocator-free0;allocator-free0 = node;current_free_index -= index;待所有的点处理完毕 ,我们还必须 整分配子中的 个员变量,
24、 max_index和current_free_index。 不要 freelist链表。allocator-max_index = max_index;allocator-current_free_index = current_free_index;while (freelist != NULL) node = freelist;freelist = node-next;free(node);上 的工作都完 ,整个点的 完毕。事实上整个内存池中的内存 是通过上 的不断地 而”起 的。一旦”内存池,下一 的 则可以直 内存池中获 。2.3.5分配子内存管理流程据上 的描述,我们现在 串起 看
25、一 整个分配子工作的流程。假如存在下 一段 1. apr_allocator_t *allocator;2. apr_allocator_create(3. apr_allocator_max_free_set(allocator, 0);/简 起见,不进行任何 4. apr_memnode_t *memnode1 = apr_allocator_alloc(allocator, 3000);5. apr_allocator_free(memnode1);6. apr_memnode_t *memnode2 = apr_allocator_alloc(allocator, 3000);7. a
26、pr_allocator_free(memnode2);8. apr_memnode_t *memnode3 = apr_allocator_alloc(allocator, 3000);9. apr_allocator_free(memnode3);一行执行完毕 ,“的分配子示意图如下图所以,该图中尚 有任何的内存 可供分配 在四行中, 要内存分配子分配2000的,但此 没有任何可供分配(index allocator-max_index, allocator-free0=NULL),因此分配子将直 向作8K 的 ,剔 ” 头 的 大 小 ,实际可 的 内 存 大 小 为 8k-APR_ME
27、MNODE_T_SIZE。执行完五行的 ,该内存将归还分配子, 存在 引1链表中。下图中的虚剔 为 前的状态,反之为 的状态。 如下图 现在我们 考虑六行和七行的执行 。 再 向分配子请3000K的内存的 ,过发现,该内存必须 引为1链表中获 。如 引1链表为NULL,则重前的步骤;Apache内存池内幕(4)2. 内存池2.4.1内存池概述在解内存分配子的概之 ,我们其实已解Apache中内存分配的 。不过Apache中内存的层 ”关 则是 内存池负责 织,其 据” apr_pool_t定义在apr_pools.c中,定义如下 struct apr_pool_t apr_pool_t *pa
28、rent;apr_pool_t *child;apr_pool_t *sibling;apr_pool_t *ref; / 于向内存池身cleanup_t *cleanups;apr_allocator_t *allocator;struct process_chain *subprocesses;apr_abortfunc_t abort_fn;apr_hash_t *user_data;const char *tag;#if !APR_POOL_DEBUGapr_memnode_t *active;apr_memnode_t *self; /* The node containing th
29、e pool itself */char *self_first_avail;#else /* APR_POOL_DEBUG */debug_node_t *nodes;const char *file_line;apr_uint32_t creation_flags;unsigned int stat_alloc;unsigned int stat_total_alloc;unsigned int stat_clear;#if APR_HAS_THREADSapr_os_thread_t owner;apr_thread_mutex_t *mutex;#endif /* APR_HAS_TH
30、READS */#endif /* APR_POOL_DEBUG */#ifdef NETWAREapr_os_proc_t owner_proc;#endif /* defined(NETWARE) */;Apache中存在的内存池个 通常 于一个, 们之形树型层 ”。每个内存池所存的内容以及其存周期都不一 ,比如连 内存池在整 个HTTP连 期存在,一旦连束,内存池 ;请求内存池则周期要相对短, 仅仅在某个请求周期内存存在,一旦请求束,请求内存池 。不过每个内存池都具有一个apr_pool_t”。整个内存池层 树通过parent、child以及sibling三个变量”起 。parent向
31、前内存池的父内存池;child向 前内存池的子内存池;而sibing则向 前内存池的兄弟内存池。因此整个内存池树”可以 图3.3描述 图3.3 内存池层 树”图在上 的图中,我们是表示层 ”,因此是 child和sibling 个员,而忽 的 parent 的变量。从上 的图中我们可以 看出 点具有 n 个孩子点 child1,child2,child3childn。而child1,child2,child3以及childn 们 于 一个父亲,而且处于层 树的 一层 ,因此 们通过链表连 , 为兄弟点。 child10和child11都是child1的子内存池 点, 为兄弟点。child21是
32、child2的唯一的子点。其余点类“。 此 之 外 apr_pool_t ” 中 最 重 要 的 员 变 量 是 active 。图3.4 Apache中提供大量的内存池管理 , 们的能和名称归纳在表 3.2中。内存池作 名称 能简 描述初始化 ap p ii ia ize 对内存池 中 要的内部变量进行初始化 ap p e ia e 主要在终止内存池 内部的”“ ap p c ea e exap p c ea e ex ebu “一个的内存池,外还 一个 试版清 ap p c ea ap p c ea ebu 清内存池中的所有的内存,外 一个 试版ap p e 2.4.2内存池的初始化内存池
33、的初始化是通过 apr_pool_initialize实现的,在内部 完下 件事情 APR_DECLARE(apr_status_t) apr_pool_initialize(void)apr_status_t rv;if (apr_pools_initialized+)return APR_SUCCESS;(1)、fl Apache中“一个 内存池,为此,Apache中 apr_pools_initialized进行录。 apr_pools_initialized初始”为0,初始化 该” 改为1。每 初始化之前都检 该”,有”为0的 才允许继续执行初始化作, 则直 。通过这种手段可以fl 有
34、一个 内存池存在。if (rv = apr_allocator_create(return rv;if (rv = apr_pool_create_ex(global_allocator = NULL;apr_pools_initialized = 0;return rv;apr_pool_tag(global_pool, “apr_global_pool“);(2)、“ 的分配子global_allocator, 分配子global_allocator“ 内存池 global_pool,该内存池是所有的内存池的祖先。所有的内存池都从该内存池继而 。在整个Apache的生存周期都存在,重启机器
35、,该内 存池 不 。你把Apache彻底关闭。该内存池在 中命名为“apr_gloabl_pool”。if (rv = apr_atomic_init(global_pool) != APR_SUCCESS) return rv;#if APR_HAS_THREADSapr_thread_mutex_t *mutex;if (rv = apr_thread_mutex_create(apr_allocator_mutex_set(global_allocator, mutex);#endif /* APR_HAS_THREADS */apr_allocator_owner_set(global
36、_allocator, global_pool);(3)、如 前的作 允许 程,为fl 内存池” 程 问的 的程安,还必须设置apr_pool_t”内的 锁变量mutex。最 的任务 是将内存分配子和内存池进行关联。Apache内存池内幕(5)2. .3内存池的“勿庸置疑,内存池的“是内存池的作之一。内存池“ 的 型如下所示 APR C AR ap a u ap p c ea e ex ap p ewp ap p pa e ap ab uc ab ap a ca a ca 其中,ewp 是 要“的的内存池,且“ 的内存池通过该 。pa e 则是 前“的内存池的父亲;ab “ 败的 所 的处理
37、;a ca 则是真进行内存分配的分配子。ap p p ap e e e ewp i !pa e pa e ba p i !ab pa e ab pa e - ab i a ca a ca pa e - a ca 在“过程中,我们没有定 前“的内存池的父亲,则将其默认为父亲为 内存池ba p , 如 内存池关联的ab 和分配子a ca 没有定义, 直 继父辈的相关信息。i e a ca a c a ca A OC - APR O T S i ab ab APR O e u APR O e- ex e e- e e- ex p ap p e- i avai e- i avai p - e i av
38、ai cha p S Ocurrency1 POO T p - a ca a ca p - ac ive p - e e p - ab ab p - chi p - c eaup p - ee c eaup p - ubp ce e p - u e a a p - a 在一切 绪之 , 将必须首先“ap p ”。但是前 我们过Apache中对所有内存 的分配都是以内存点ap e e 进行分配的,而且每 分配的最小 元为 K,这对于“ap p ” 不例外。因此 将首 先 分配子a ca 分配 K的内存, 将最顶端的内存分配ap e e ”。此 着ap e e ”下 的内存才能继续分配ap p ,
39、 表示内存池”,ap p ”之 才是真可 的。在整个 K内存中,点头和内存池头 部分别 的大小为APR O T S 和S Ocurrency1 POO T,因此 真可 的实际上有 k- APR O T S -S Ocurrency1 POO T大小,至此我们还必须要 整ap e e 中的 i avai 针和ap p ”中的 e i avai 针向真可 。过 轮分配之 , K内存的 如图3.所示 一旦完内存池点的分配工作,我们必须将其挂内存池层 树上。挂的过程 是设置pa e ,chi 以及 ib i 的过程。i p - pa e pa e ! i p - ib i pa e - chi ! p
40、 - ib i - e p - ib i pa e - chi p p - e pa e - chi e e p - ib i p - e ewp p 挂的过程可以分为下 个步骤 1、将 前的点的pa e 针向父点,p - pa e pa e 。2、设定 前点的 ibi 。 ibi 应该向 与 前点处于 一层 ,且父点 相的点,的点是插入子点链表的首部,插入通过下 的 实现 p - ib i pa e - chi pa e - chi p 不过如 父点为,意 着该点 有兄弟点,故p - ib i 。3、设置 e 员。在ap p 中, e 于向在内存池点“的过程中,我们可以看,内存池“ ac i
41、ve 为。因此 前内存池中能够 的内 存仅仅为 k- APR O T S -S Ocurrency1 POO T大小。如 从内存池中请 的内存的 , ,此 必须通过ac ive 扩 该内存池对应的内存点。这一点我们可以在内存池的内存分配中看出 。Apache内存池内幕(6)2. . 内存池的内存分配从内存池中分配内存通过 个 实现 ap pca c和ap pa c,这 个 唯一的区别 是ap pca c分配 的内存部 清 ,而ap pa c则这一步的工作。ap pa c的 型如下所示 APR C AR v i ap pa c ap p p ap ize ize中p 是 要分配内存的内存池, i
42、ze则是 要分配的内存的大小。一旦分配则 分配内存的地址。在解内存池的内存分配之前,我们应该对ac ive链表有所解。 名 义,ac ive链表中 存的都是 在 的 ap e e 内存点。这 点都是 分配子进行分配,之所以 ,一个重要的 因 是 们有足够闲的。将这 点 存在 ac ive上,这 下 要内存的 要首先遍历ac ive链表可,有在ac ive链表中的点不能够 足分配要求的 才 重 分配子 请的内存。一fi ,一旦某个点中进入ac ive链表, 不能在 先的分配子链表中存在。对于每一个ap e e 内存点, 的实际可 为e p- i avai 的大小。但是如前 所,Apache中 量
43、 通常 引的fi,对于所有的点, 的闲ee i ex描述。为 度,ac ive链表中的所有的点 其的大小 进行反向 序,为此闲大得是 在前 ,闲最小的则肯定 在最末尾。对于定的分配,要将其与一个点的闲进行比较,如 一 个闲都不 足, 此必须向分配子重请, 则直 从一个点中分配, 整分配 的点 序。ap e e ac ive e v i e ap ize ee i ex ize APR A currency1A T ize ac ive p - ac ive i ize ap ize ac ive- e p - ac ive- i avai e ac ive- i avai ac ive- i
44、avai ize e u e 分配首先 要分配的实际,这 都是 对 整过的。Apache首先尝试ac ive链表 的一个点中分配,如前 所言,这个是链表中闲最 的点,如能够 足 要,Apache直 ize大小的, 整的 i avai 针。不过这 要注意的是对于链表的情况。 一个内存池 ap c ea e p ex“以 , 的 ac ive链表为,不过此 ac ive不为 ,事实上ac ive e,意 着ac ive向内存池所在的内存点。因此这种情况 下,的分配不 败。 e ac ive- ex i ize ap ize e- e p - e- i avai i e ve e e e i e a ca a c p - a ca ize i p - ab p - ab APR O e u 如 ac ive链表中的点都不能 足分配 求, 此 唯一能够 的 是直 向分配子请的。至于分配子如何分配,是从池中获 还是直 a c分配,此处不再 。 e- ee i ex e e- i avai e- i avai ize i i e e ac