1. 对内存分配子的字段总结:
struct apr_allocator_t {
apr_uint32_t max_index;
apr_uint32_t max_free_index;
apr_uint32_t current_free_index;
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
#endif /* APR_HAS_THREADS */
apr_pool_t *owner;
apr_memnode_t *free[MAX_INDEX];
};
首先需要指出,分配子中的free数组中的内存都是可用内存,也就是说还没有被使用。
max_index:free指针数组的下标,它指的是free数组中已存在的最大内存块链表的索引值。
max_free_index:用于记录内存分配子中最多可用容纳的内存空间大小。
current_free_index:用于记录内存分配子中当前可以继续容纳的内存空间的大小,该字段和max_free_index一起用于控制内存分配子的内存大小。
owner:指示该分配子属于哪个内存池。
free:指向一组链表的头结点,该链表中每个结点指向内存结点组成的链表。
内存分配子的结构图如下:
在Apache源代码中,每个内存结点的字段index的计算方式如下:
index=(size>>BOUNDARY)-1 其中BOUNDARY=12
但是,在计算调整后的max_free_index字段时,并没有减去1,比如在函数apr_allocator_max_free_set中,
max_free_index=APR_ALIGN(size,BOUNDARY_SIZE)>>BOUNDARY_INDEX;
其中BOUNDARY_SIZE=1<<BOUNDARY_INDEX ,即BOUNDARY_SIZE=2^12=4K
因此max_free_index仅仅记录的是内存大小相对于4K的整数值。current_free_index同样如此,这一点可以在内存分配子的结点回收函数allocator_free中可以体现,而max_index是与index对应的,因此也减去过1。见下面加黑和加红部分:
void allocator_free(apr_allocator_t *allocator, apr_memnode_t *node)
{
apr_memnode_t *next, *freelist = NULL;
apr_uint32_t index, max_index;
apr_uint32_t max_free_index, current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif /* APR_HAS_THREADS */
max_index = allocator->max_index;
max_free_index = allocator->max_free_index;
current_free_index = allocator->current_free_index;
/* Walk the list of submitted nodes and free them one by one,
* shoving them in the right 'size' buckets as we go.
*/
do {
next = node->next;
index = node->index;
if (max_free_index != APR_ALLOCATOR_MAX_FREE_UNLIMITED
&& index + 1 > current_free_index) {
node->next = freelist;
freelist = node;
}
else if (index < MAX_INDEX) {
/* Add the node to the appropiate 'size' bucket. Adjust
* the max_index when appropiate.
*/
if ((node->next = allocator->free[index]) == NULL
&& index > max_index) {
max_index = index;
}
allocator->free[index] = node;
if (current_free_index >= index + 1)
current_free_index -= index + 1;
else
current_free_index = 0;
}
else {
/* This node is too large to keep in a specific size bucket,
* just add it to the sink (at index 0).
*/
node->next = allocator->free[0];
allocator->free[0] = node;
if (current_free_index >= index + 1)
current_free_index -= index + 1;
else
current_free_index = 0;
}
} while ((node = next) != NULL);
allocator->max_index = max_index;
allocator->current_free_index = current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif /* APR_HAS_THREADS */
while (freelist != NULL) {
node = freelist;
freelist = node->next;
#if APR_ALLOCATOR_USES_MMAP
munmap(node, (node->index+1) << BOUNDARY_INDEX);
#else
free(node);
#endif
}
}
2.Apache中提供的分配子相关函数
分配子的创建 apr_allocator_create
分配子的销毁 apr_allocator_destroy
分配子的空间分配 apr_allocator_alloc 调用分配子分配一定的空间
分配子的空间释放 apr_allocator_free 释放分配子已经分配的空间,将它返回给分配子
分配子的其余设置
apr_allocator_owner_set 设置分配子所属的内存池
apr_allocator_owner_ger 获取分配子所属的内存池
apr_allocator_max_free_set 设置分配子可以容纳的最大内存,即max_free_index字段,当然current_free_index会跟 着变化
3.分配子相关函数的分析
分配子的创建和销毁相对较容易理解,这里主要分析分配子的空间分配和释放两个函数。
(1)apr_allocator_alloc
该函数内部,实际上调用的是allocator_alloc函数。下面具体分析:
static APR_INLINE apr_memnode_t *allocator_alloc(apr_allocator_t *allocator, apr_size_t in_size)
{
apr_memnode_t *node, **ref;
apr_uint32_t max_index;
apr_size_t size, i, index;
/*该函数所做的第一件事就是分配原则重新调整实际需要分配的空间大小。这里面的调整包括两个部分:首先是对内存结 点本身的大小进行调整,即APR_MEMNODE_T_SIZE;其次,需要分配的空间大小in_size和调整后的内存结点之和也得进行调整,调整后的大小用size表示。另外,这里是调整的原则是以4K对齐,因为BOUNDARY_SIZE为2^12,即4K 。*/
size=APR_ALIGN(in_size + APR_MEMNODE_T_SIZE, BOUNDARY_SIZE);
/*如果调整失败,则返回NULL。*/
if (size < in_size) {
return NULL;
}
/* 如果调整后的大小小于规定的最小分配空间8K,则需要重新设置。*/
if (size < MIN_ALLOC)
size=MIN_ALLOC;
/* 计算该空间大小size所对应的索引值. */
index= (size >> BOUNDARY) -1;
if (index > APR_UINT32_MAX) {
return NULL;
}
/* 首先判断需要分配的结点大小若属于规则结点,并且在分配子中存在,即index<=allocator->max_index */
if (index <= allocator->max_index) {
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif
max_index = allocator->max_index;
/* ref 是一个二级指针,这里将它指向索引 index 所在数组的地址。 */
ref=&allocator->free[index];
i = index;
/* 正常情况下,能够满足分配的最小结点就是index索引对应的链表结点,但该索引对应的链表可能为空,因此需要 继续向后查找,直到第一个可用的不为空结点或者到索引为max_index链表。当循环退出的时候,有两种结果:第一,找到了一个索引值小于max_index的非空链表,这时便可以进入链表里进行实际的内存分配。第二,从index开始知道max_index索引之前的所有链表都是空的,这种情况下循环会退出,并且
i=max_index 。总的来说,循环退出后,ref指向对应的索引链表地址,i 为对应的索引值。*/
while (*ref == NULL && i<max_index) {
ref++;
i++;
}
/* 无论是上述哪种情况,都是在规则结点里,下面就该对找到的结点进行处理,这里可能会出现两种情况:一、找到的链表有两个或以上的内存结点,这种情况下把第一个结点分配出去,并且把第二个内存结点链接到索引对应的数组上,这是通过 node = *ref 和 *ref = node->next 这两步实现的。二、找到的链表只有一个内存结点,这种情况下,当把该结点分配出去后,该索引对应的链表设置为空,这里同样是通过 *ref =
node->next 实现的,另外,此时分配子的字段max_index必须相应的变化,一步一步往索引值减一方向判断,直到 *ref对应的链表不为空,ref--主要实现的是ref指针往数组小的方向移动,最后循环退出后ref指向最大内存块的数组元素上,max_index即为最大内存块的数组索引值。*/
if ((node = *ref) != NULL) {
if ((*ref = node->next) == NULL && i>=max_index) {
do {
ref--;
max_index--;
}
while (*ref == NULL && max_index>0);
allocator->max_index=max_index;
}
/* 动态调整分配子字段current_free_index的大小,由于内存结点字段index计算时减掉一个1,这时必须加上。这里还有一个问题,当allocator->current_free_index > allocator->max_free_index这种情况出现时,多余的内存空间是如何释放掉给系统的。*/
allocator->current_free_index+=node->index+1;
if (allocator->current_free_index > allocator->max_free_index)
allocator->current_free_index = allocator->max_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif
/*上述工作完成后,剩下就是返回结点了,返回前需要将结点取下来,因为此时node还是一个结点链表,因此需要设置结点node的next字段为NULL,至于为什么要设置first_avail字段,目前还没想清楚。 */
node->next=NULL;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
return node;
}
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif
}
/* 当分配的结点大小超过了规则结点中已存在的最大结点,则函数将考虑从索引0链表上获取空间。*/
else if ( allocator->free[0]) {
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif
/* 把索引为0的数组元素地址赋给二级指针ref 。*/
ref = &allocator->free[0];
/* 遍历索引0链表以找到满足要求的第一个结点。*/
while ((node=*ref) != NULL && index >node->index)
ref = &node->next;
if (node) {
/* 把剩下的链表给重新链接起来,仍然是通过 node=*ref 和 *ref = node->next 这两步来实现的。*/
*ref = node->next;
/* 同上面那种情况。*/
allocator->current_free_index += node->index + 1;
if (allocator->current_free_index > allocator->max_free_index)
allocator->current_free_index = allocator->max_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif
/* 同上面那种情况。*/
node->next = NULL;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
return node;
}
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif
} /* end if allocator->free[0] */#if APR_ALLOCATOR_USES_MMAP
if ((node = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0) ) == MAP_FAILED)
#else
/* 如果在规则链表和索引0链表里都没有合适的空间供分配,此时就只能从操作系统中调用malloc函数来分配空间。*/
if ((node = malloc(size) ) == NULL)
#endif
return NULL;
/* 设置结点node的各个字段。*/
node->next = NULL;
node->index = (APR_UINT32_TRUNC_CAST)index;
node->first_avail = (char *)node + APR_MEMNODE_T_SIZE;
node->endp = (char *)node + size;
return node;
}
这里分配子内存的释放并不是真正的调用free函数释放,而只是将空间回收到分配子链表池中。
Apache源代码中,apr_allocator_free的实现实际上是通过allocator_free函数来实现的。下面具体分析该函数。
static APR_INLINE void allocator_free(apr_allocator_t *allocator, apr_memnode_t *node)
{
apr_memnode_t *next, *freelist = NULL;
apr_uint32_t index, max_index;
apr_uint32_t max_free_index, current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_lock(allocator->mutex);
#endif
max_index = allocator->max_index;
max_free_index = allocator->max_free_index;
current_free_index = allocator->current_free_index;
/* 由于node可能是一个结点或者一个结点链表,因此若要完全释放,必须对结点逐个遍历,这里便是通过do ... while 循环来实现的。*/
do {
/* 记录下node结点的下一个结点*/
next = node->next;
index = node->index;
/* 如果分配子可以容纳的最大内存有限制(即max_free_index不为0),则必须考虑释放的当前结点大小是否大于当前可以容纳的空间大小current_free_index。如果超过了,则必须将该结点先放进一个链表freelist中,最后还给操作系统。每次需要释放给操作系统的结点都是插入到freelist的第一个结点前,这是通过 node->next=freelist 和 freelist=node这两步实现的。*/
if (max_free_index != APR_ALLOCATOR_MAX_FREE_UNLIMITED && index+1 > current_free_index) {
node->next=freelist;
freelist = node;
}
/* 如果结点属于规则结点的范围,即index<MAX_INDEX的情况。这时候又会有两种情况:一、index >max_index,这种情况下将结点挂接于free[index]链表的第一个结点前,并且修改max_index的值。二、index<=max_index的情形,这时只需把结点挂接于对应的索引链表上即可。挂接链表的操作主要是通过 node->next = allocator->free[index] 和 allocator->free[index] = node 这两步实现的。当然,释放完后,还需动态调整current_free_index的大小。*/
else if ( index < MAX_INDEX) {
if ( (node->next = allocator->free[index]) == NULL && index>max_index) {
max_index=index;
}
allocator->free[index] = node;
if (current_free_index >= index + 1)
current_free_index -= index+1;
else
current_free_index = 0;
}
/* 如果结点超过了规则结点的范围,但是并没有超出回收结点的范围,这是就应当将其回收到索引0链表的首个结点前。这里是通过 node->next = allocator->free[0]和 allocator->free[0] = node实现的。挂接完后,也要对current_free_index字段进行调整。*/
else {
node->next = allocator->free[0];
allocator->free[0] = node;
if (current_free_index >= index + 1)
current_free_index -= index+1;
else
current_free_index = 0;
}
} while ( (node = next) != NULL);
/*这里需要对分配子的两个字段进行重新设置。*/
allocator->max_index = max_index;
allocator->current_free_index = current_free_index;
#if APR_HAS_THREADS
if (allocator->mutex)
apr_thread_mutex_unlock(allocator->mutex);
#endif
/*释放freelist结点链表。*/
while (freelist != NULL) {
node = freelist;
freelist = node->next;
#if APR_ALLOCATOR_USES_MMAP
munmap(node, (node->index + 1) << BOUNDARY_INDEX);
#else
free(node);
#endif
}
}
本文深入分析了Apache内存分配子的结构,包括max_index、max_free_index和current_free_index字段的作用,以及内存结点的管理。同时,讲解了内存分配子的相关函数,如创建、销毁、分配和释放空间的实现细节,特别是apr_allocator_alloc和allocator_free函数的工作原理。通过对内存分配和释放过程的详细解析,展示了Apache内存管理的高效策略。
1479

被折叠的 条评论
为什么被折叠?



