2013年(18)
分类: LINUX
2013-05-14 17:34:03
Apache服务器的开发人员将代码中可移植的部分整理出来,编辑成Apache可移植运行库(Apacheportable Run-timelibraries),简称APR,该库可从下载,其中包含这里要介绍的内存池的实现代码。下面将Apache服务器内存池简称为APR内存池。
在了解整个内存池架构前,我们先来了解APR内存池中最基本的单元——内存分配结点。内存分配结点被用来描述每次分配的内存块,对应的结构名为apr_memnode_t,定义在文件apr_allocator.h中,其定义如下:
/* basic memory nodestructure*/
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 */
};
|
即使结构中的每个字段,源文件中都给出了注释,但对于每个字段的用途,还是很难让人理解。这里先给出每个字段的简略解析:
该结点示意图如下:
在ARP内存池中,使用内存分配器对内存分配结点进行管理,它在apr_pools.c中定义如下:
struct apr_allocator_t {
apr_uint32_t max_index;
apr_uint32_t max_free_index;
apr_uint32_t current_free_index;
apr_pool_t *owner;
apr_memnode_t *free[MAX_INDEX];
};
|
内存分配器及其管理的内存结点图示如下:
从上图我们可以清晰地看出,free数组的下标从1到MAX_INDEX-1,分别指向一条结点大小固定的链表,下标增加1,结点的大小增加4k,因此free[MAX_INDEX]所指向的链表的结点大小为84k,这也是内存池使用者所能申请的最大”规则结点“,超过该大小的结点将下标0指向的链表进行管理。要明白free数组下标和结点大小的关系,我们需要知道宏定义APR_ALIGN:
#defineAPR_ALIGN(size, boundary) \
(((size)+ ((boundary) - 1)) &~((boundary) - 1))
|
该宏所做的无非就是计算出最接近size的boundary的整数倍的整数。通常情况下size大小为整数即可,而boundary则必须保证为2的倍数。比如APR_ALIGN(7,4)为8;APR_ALIGN(21,8)为24;APR_ALIGN(21,16)则为32。
对于每次空间申请,APR先对齐空间大小:
size = APR_ALIGN(size +APR_MEMNODE_T_SIZE, 4096);
|
结果是size的值变成4096(4k,2的12次方)的倍数,最后,通过左移与我们的下标对应起来:
index= (size >>BOUNDARY_INDEX) - 1; //BOUNDARY_INDEX=12
|
APR的内存池结点,在apr_pools.c文件中定义如下:
struct apr_pool_t {
……
apr_allocator_t *allocator;
apr_memnode_t *active;
……
};
|
该结构中的字段比较多,我们主要关注以上列出的两个字段。
内存池结点及其管理的内存结点如下图所示:
需要留意的是虽然该结构字面意义上为“内存池结构”,但是它负责管理使用中的内存。
对APR内存池各个结构有了初步了解之后,我们来看APR中是如何利用这些结构进行内存管理的。
内存申请的核心函数是allocator_alloc函数,参数为一个指向内存分配器的指针和所要申请空间的大小,内存分配就是对内存分配器进行操作(以下列出的字段参见“内存分配器”章节),其内存申请的策略如下:
在内存申请中提到,假如内存分配器中没有合适的内存块,将会调用malloc获取一块,但是新分配的内存并不挂接到内存分配器链表中,而是在调用allocator_free进行内存释放的时候,内存才可能挂到内存分配器链表上。内存释放策略如下:
刚开始,由内存分配器管理的链表并没有挂接任何内存,也就是说内存池是空的,当我们申请内存时,必然进行“内存申请”中的第三步操作,新分配的内存就由我们的内存池结点进行管理。
先来看用于内存池结点管理的一个宏定义:
/* Node list managementhelper macros; list_insert() inserts 'node'
* before 'point'. */
#define list_insert(node,point) do { \
node->ref= point->ref; \
*node->ref= node; \
node->next= point; \
point->ref= &node->next; \
} while (0)
|
在内存池结点初次建立时,链表状态如下图:
再次申请一个结点,运行list_insert(node,point)后,结果如下图:
我们可以通过allocator_free调用或apr_pool_clear、apr_pool_destroy调用(它们内部调用allocator_free)进行内存池结点的释放,所释放的内存将按照“内存释放”中的策略归还内存池或操作系统。
free数组下标的双重含义(即是数组下标,又指示内存块大小)、通过阙值max_free_index限制内存池大小,这些都较难理解,但也是是APR内存池实现中出彩的部分,另外,APR内存池还涉及到父/子/兄弟内存池、内存池生命周期的概念,同学们可以通过文章末尾给出的参考文档,进行更深入地学习