分类: C/C++
2012-03-04 03:36:06
今天来探讨一下Nginx的内存分配策略。
那咱们先看看内存池长什么样子吧。
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup;
ngx_log_t *log;
};
我们从实际函数中来研究这个内存池是怎样运作、那些成员有什么用,先来看看怎样创建内存池:
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;size = size - sizeof(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;return p;
}
好了,可以看到,创建内存池是初始化一些成员,设置大小。
接着,我们从内存池分配内存用的是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清
0的内存,第三个函数分配的内存不会对齐。
我们这里看第一个。
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;if (size <= pool->max) {
p = pool->current;
do {
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size;return m;
}p = p->d.next;
} while (p);
return ngx_palloc_block(pool, size);
}return ngx_palloc_large(pool, size);
}
这里可以知道内存池连起来的。
看到代码 if (size <= pool->max) 就是看一个内存池最大的大小够不够你放,够的话,进入里面。
p = pool->current; 这一句说明current成员指向的是当前内存池。
接下来是一个循环,就是要找当前内存池中还够不够你需要的大小,不够就找下一块内存池。
如果每个内存池都找不到,就ngx_palloc_block。
如果从一开始if (size <= pool->max) 这里就知道一个内存池根本满足不了欲望,好,那就ngx_palloc_large。
那么ngx_palloc_block和ngx_palloc_large究竟有什么作用呢?
我们接着看ngx_palloc_block的代码:
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new, *current;psize = (size_t) (pool->d.end - (u_char *) pool);
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}new = (ngx_pool_t *) m;
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;current = pool->current;
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
current = p->d.next;
}
}p->d.next = new;
pool->current = current ? current : new;
return m;
}
显然,这个函数是新建一个全新的内存池,并加到现有的内存池链条中。
接着看ngx_palloc_large:
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}n = 0;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}if (n++ > 3) {
break;
}
}large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}large->alloc = p;
large->next = pool->large;
pool->large = large;return p;
}
这里补充一下内存池中有一个large的指针,类型为
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
可以看到这是一个链表,存着真实的数据,而在内存池里面仅仅存放一个链表。
代码很容易理解。注意这里用了些技巧,把新建的大块内存确保在链表中前四的位置。
迟些时候继续分析销毁内存池。
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;for (c = pool->cleanup; c; c = c->next) {
if (c->handler) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"run cleanup: %p", c);
c->handler(c->data);
}
}for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
if (l->alloc) {
ngx_free(l->alloc);
}
}#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p, unused: %uz", p, p->d.end - p->d.last);if (n == NULL) {
break;
}
}#endif
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);if (n == NULL) {
break;
}
}
}
其实这个很容易看懂,如果我们原来设有内存池的清理函数,则调用。然后释放大块内存,最后释放小块,都是用ngx_free(也就是free)掉。