Apache 中使用的 APR Memory Pool 分析



简介
APR 中的Memory Pool是内存管理基础模块,所有其他的模块,只要用到的内存分配,都要用到这个模块,相关的结构中都有apr_pool_t参数
pool本身并不直接从物理内存中分配或释放,而是通过allocator(内存分配器)来统一管理,可以为新池创建新的allocator(内存分配器),但通常使用默认的全局allocator(内存分配器),这样更有助于统一的内存管理
pool采用的是树形的结构,在初始化内存池(apr_pool_initialize)时,建立根池,和全局allocator(内存分配器),以后建立的都是根结点的子孙结点
可以从pool中分配任何大小的内存块,但释放的单位为pool,就是说pool释放之前,从pool分配出的内存不能单独释放,看起来好像有点浪费.
这里要注意的是,有些分配的内存块,清除时有特别操作,这样就需要要带清除函数,在分配之后用apr_pool_cleanup_register注册清除时用的函数。
特殊的,如果内存块里是线程对象,也不能用一般的清除函数,应该用apr_pool_note_subprocess注册清除操作

[结构体]
//池的结构
struct apr_pool_t {
apr_pool_t *parent; //父池结点
apr_pool_t *child; //子池结点
apr_pool_t *sibling; //兄弟池结点
apr_pool_t **ref; //指向-指向自已的指针,一般是前继结点的next指针
cleanup_t *cleanups; //需要清除函数来销毁的内存块指针列表,通过注册来加入的
cleanup_t *free_cleanups;//当Kill 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_DEBUG
apr_memnode_t *active; //拥有的内存结点
apr_memnode_t *self; /* The node containing the pool itself */
char *self_first_avail;

#else /* APR_POOL_DEBUG */
apr_pool_t *joined; /* the caller has guaranteed that this pool
* will survive as long as ->joined */
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_THREADS
apr_os_thread_t owner;
apr_thread_mutex_t *mutex;
#endif /* APR_HAS_THREADS */
#endif /* APR_POOL_DEBUG */
#ifdef NETWARE
apr_os_proc_t owner_proc;
#endif /* defined(NETWARE) */
};

[函数]
对系统内存池初始化,全局的,一个进程只要初始化一次
apr_status_t apr_pool_initialize (void)
销毁内存池对象,及内部的结构和子内存池
void apr_pool_terminate (void)

创建一个新的内存池
apr_status_t apr_pool_create_ex (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator)
创建一个新的内存池,apr_pool_create_ex的使用默认参数简化版
apr_status_t apr_pool_create (apr_pool_t **newpool, apr_pool_t *parent)
获取内存池使用的内存分配器
apr_allocator_t * apr_pool_allocator_get (apr_pool_t *pool)
清除一个内存池的内容,清除后内容为空,但可以再使用
void apr_pool_clear (apr_pool_t *p)
释构一个内存池
void apr_pool_destroy (apr_pool_t *p)

从池中分配内存
void * apr_palloc (apr_pool_t *p, apr_size_t size)
从池中分配内存,并将分配出来的内存置0
void * apr_pcalloc (apr_pool_t *p, apr_size_t size)

设置内存分配出错时的调用函数
void apr_pool_abort_set (apr_abortfunc_t abortfunc, apr_pool_t *pool)
获取内存分配出错时的调用函数
apr_abortfunc_t apr_pool_abort_get (apr_pool_t *pool)

获取池的父池
apr_pool_t * apr_pool_parent_get (apr_pool_t *pool)

判断a是否是b的祖先
int apr_pool_is_ancestor (apr_pool_t *a, apr_pool_t *b)

为内存池做标签
void apr_pool_tag (apr_pool_t *pool, const char *tag)

设置与当前池关联的数据
apr_status_t apr_pool_userdata_set (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
设置与当前池关联的数据,与apr_pool_userdata_set类似,但内部不拷贝数据的备份,如常量字符串时就有用
apr_status_t apr_pool_userdata_setn (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
获取与当前池关联的数据
apr_status_t apr_pool_userdata_get (void **data, const char *key, apr_pool_t *pool)
注册内存块的清除函数,每块销毁时要特别处理的都要注册下,在cleanups里加入一个项
void apr_pool_cleanup_register (apr_pool_t *p, const void *data, apr_status_t(*plain_cleanup)(void *), apr_status_t(*child_cleanup)(void *))
删除内存块的清除函数,从cleanups里移除一个项,放入free_cleanups中
void apr_pool_cleanup_kill (apr_pool_t *p, const void *data, apr_status_t(*cleanup)(void *))
用新的child_cleanup,替换原来老的child_cleanup
void apr_pool_child_cleanup_set (apr_pool_t *p, const void *data, apr_status_t(*plain_cleanup)(void *), apr_status_t(*child_cleanup)(void *))
执行内存块的清除函数,进从清除函数的队列cleanups中删除
apr_status_t apr_pool_cleanup_run (apr_pool_t *p, void *data, apr_status_t(*cleanup)(void *))
一个空的内存块清除函数
apr_status_t apr_pool_cleanup_null (void *data)
执行所有的子清除函数child_cleanup
void apr_pool_cleanup_for_exec (void)

带调试信息内存池函数,功能跟上面的一样,只是多了调试信息
apr_status_t apr_pool_create_ex_debug (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator, const char *file_line)
void apr_pool_clear_debug (apr_pool_t *p, const char *file_line)
void apr_pool_destroy_debug (apr_pool_t *p, const char *file_line)
void * apr_palloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)
void * apr_pcalloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)

<一>内存池初始化 apr_pool_initialize
1.一个进程只在第一次调用时初始化,后面只记录调用的次数
if (apr_pools_initialized++)
return APR_SUCCESS;
2.创建全局内存分配器(global_allocator)
if ((rv = apr_allocator_create(&global_allocator)) != APR_SUCCESS) {
apr_pools_initialized = 0;
return rv;
}
3.创建全局内存池,根内存池
if ((rv = apr_pool_create_ex(&global_pool, NULL, NULL,
global_allocator)) != APR_SUCCESS) {
apr_allocator_destroy(global_allocator);
global_allocator = NULL;
apr_pools_initialized = 0;
return rv;
}
4.为内存池做标记
apr_pool_tag(global_pool, "apr_global_pool");
5.初始化原子操作
有什么想法可以发邮件 fastxyf at hotmail.com,一起交流
6.对于支持多线程的系统,还创建互拆体,并设置到内存分配器
#if APR_HAS_THREADS
{
apr_thread_mutex_t *mutex;

if ((rv = apr_thread_mutex_create(&mutex,
APR_THREAD_MUTEX_DEFAULT,
global_pool)) != APR_SUCCESS) {
return rv;
}

apr_allocator_mutex_set(global_allocator, mutex);
}
#endif /* APR_HAS_THREADS */

7.设置内存分配器的池所有者
apr_allocator_owner_set(global_allocator, global_pool);

<二>销毁内存池对象 apr_pool_terminate
1.如果调用了多次内存池初始化,则只有在调用了相应次数的销毁时,最后一次才进行操作
if (!apr_pools_initialized)
return;
if (--apr_pools_initialized)
return;
2.释放全局内存池,同时会释放想应的互拆体,如果内存分配器的所有者是本内存池,则一同释放内存分配器
apr_pool_destroy(global_pool);

<三>创建一个新的内存池 apr_pool_create_ex
参数说明:
newpool 新创建的内存池指针,用于返回的.
parent 父内存池. 如果为空,则父内存池为根内存池,非空时,新的内存池继承父内存池的属性.
abort_fn 分配内存失败时的回调函数,中断函数
allocator 新内存池所用的内存分配器. NULL则使用父内存池的内存分配器
1.先确定内存池的参数,如父结点,出错处理函数,所用的内存分配器
if (!parent)
parent = global_pool;
if (!abort_fn && parent)
abort_fn = parent->abort_fn;
if (allocator == NULL)
allocator = parent->allocator;
2.从内存分配器一块内存块,用于存贮内存池的信息,如果内存分配失败,则调用中断函数
if ((node = allocator_alloc(allocator,
MIN_ALLOC - APR_MEMNODE_T_SIZE)) == NULL) {
if (abort_fn)
abort_fn(APR_ENOMEM);

return APR_ENOMEM;
}
3.用新分配内存块,存贮内存池的结构apr_pool_t
node->next = node;
node->ref = &node->next;

pool = (apr_pool_t *)node->first_avail;
node->first_avail = pool->self_first_avail = (char *)pool + SIZEOF_POOL_T;

pool->allocator = allocator;
pool->active = pool->self = node;
pool->abort_fn = abort_fn;
pool->child = NULL;
pool->cleanups = NULL;
pool->free_cleanups = NULL;
pool->subprocesses = NULL;
pool->user_data = NULL;
pool->tag = NULL;
物理内存块的内容如下
|----------------|
| apr_memnode_t | 占用大小 APR_MEMNODE_T_SIZE
| ---------------|
| apr_pool_t | 占用大小 SIZEOF_POOL_T
| ---------------|
| 可用空间 | 占用大小 8K - APR_MEMNODE_T_SIZE - SIZEOF_POOL_T
| ---------------|
4.系统支持多线程,则创建互拆体.
5.设置兄弟结点,及父结点
if ((pool->sibling = parent->child) != NULL)
pool->sibling->ref = &pool->sibling;

parent->child = pool;
pool->ref = &parent->child;

apr_pool_create 是使用了默认的内存分配器,和中断函数的apr_pool_create_ex简化版
#define apr_pool_create(newpool, parent) \
apr_pool_create_ex(newpool, parent, NULL, NULL)

<四> 从池中分配一块内存 apr_palloc
参数说明:
pool 要分配内存的池
size 要分配的内存大小
1.先按8字节对齐size
size = APR_ALIGN_DEFAULT(size);
2. 如果首结点有足够的空闲空间,则从这里分配
if (size < (apr_size_t)(active->endp - active->first_avail)) {
mem = active->first_avail;
active->first_avail += size;

return mem;
}
3.跟链表首结点比较(因为链表结点是按空闲空间的大小,由多至少排序的,所以首结点不够,后面的也不用比了),空间够就从链表头结点分配,因为分配后链表头结点空闲空间不一定还最多了,所以要取出,后面重新插入
如果链表首结点空间不够,则调用内存分配器,新分配一块内存

if (size < (apr_size_t)(node->endp - node->first_avail)) {
list_remove(node);
}
else {
if ((node = allocator_alloc(pool->allocator, size)) == NULL) {
if (pool->abort_fn)
pool->abort_fn(APR_ENOMEM);

return NULL;
}
}
4.将首结点或新分配的结点node,插到队列首部,再根据node里的空间大小,选择插到的位置
这里没看懂,为什么要插入?到后面找又先移出,再插到正确位置.不能改成直接在后面一次插入?
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);

apr_pcalloc 是在apr_palloc分配的内存上,增加了对分配的内存块置0
size = APR_ALIGN_DEFAULT(size);
if ((mem = apr_palloc(pool, size)) != NULL) {
memset(mem, 0, size);
}

<五>清空池内容,释放占用的内存,但保留池对象 apr_pool_clear
1.如果存在子池,则释放所有子池
while (pool->child)
apr_pool_destroy(pool->child);
2.调用清除函数,清除注册了销毁函数的内存块,每块需要销毁函数的内存块都要单独注册
run_cleanups(&pool->cleanups);
3.清除包括子线程的内存块
free_proc_chain(pool->subprocesses);
4.清除附在本内存池上的内存块,回到刚分配内存池的状态
*active->ref = NULL;
allocator_free(pool->allocator, active->next);
active->next = active;
active->ref = &active->next;

<六>释构一个内存池 apr_pool_destroy
执行的操作和apr_pool_clear差不多,增加了删除互拆体,和释放内存池结构占用的块
如果所用的内存分配器的所有者,是本内存池,也一并析构

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值