代码路径:\nginx-1.4.4\src\core\ngx_palloc.c
小结:经过多次的申请之后,该pool的结构如下(暂时不考虑ngx_pool_cleanup和log指针所指向的内容):
从代码中可以看到,仅仅是释放pool->large链表所链接的大块内存。
一、内存池的创建
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_pool_t的结构
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;
};
成员d的结构
typedef struct {
u_char *last; //p + sizeof(ngx_pool_t) 可分配的起始位置
u_char *end; //p + size 可分配的结束位置
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
对于内存块大小的解释,在X86平台上最大4095
/*
* NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
* On Windows NT it decreases a number of locked pages in a kernel.
*/
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
ngx_create_pool()后的结构如图(暂时不考虑ngx_pool_cleanup和log指针所指向的内容):
二、内存块的申请
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);
}
该函数的分配分为三种情况:
1)size <= pool->max,属于小块内存,现有的pool里面能找到合适的
//找到pool里面分配的当前块
//p所指向的块大小能够分出来就返回首地址,更新last指针偏移;不能分出来就移动p到下一个资源块尝试;
2)size <= pool->max,属于小块内存,在现有的pool里面无法找到合适的,需要调用ngx_palloc_block()进行分配
第1)种情况会出现现有的资源块都被分完的状况下,分不出来了。那么就会调用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;
}
新申请一块以前的块那么大的新块,链接到p->d.next后面,并更新last和end指针,给出新分配的首地址。
疑问:遍历p,判定failed次数大于4的目的和依据是?
3)属于大块内存,需要调用ngx_palloc_large()进行分配
区别于1)和2)两种情况,本分支为大块内存,直接调用标准malloc分配,首先到large链表中去查找,找到就更新large->alloc;找不到就需要将新的内存+内存管理头一起挂到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;
}
小结:经过多次的申请之后,该pool的结构如下(暂时不考虑ngx_pool_cleanup和log指针所指向的内容):
三、内存块的释放
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p", l->alloc);
ngx_free(l->alloc);
l->alloc = NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
从代码中可以看到,仅仅是释放pool->large链表所链接的大块内存。
四、销毁内存池
void
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);
}
}
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
1)遍历pool->cleanup链表,执行c->handler函数;
2)遍历pool->large链表,释放l->alloc内存;
3)遍历pool链表,释放pool指向内存;
五、对内存释放的思考
引述其它blog内容如下:
nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成;
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。
也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。
这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。
小结:通过内存的分配和释放可以看出,nginx只是将小块内存的申请聚集到一起申请,然后一起释放。避免了频繁申请小内存,降低内存碎片的产生等问题。