nginx的设计思想是小块内存用自己的内存池pool分配,大块内存用malloc分配,并且大块内存和小块内存的管理结构都存储在pool中,由他们管理.
nginx采用多次分配,一次释放,即一次connect或request完成后,大块小块内存一同释放.
看一下内存池的基本数据结构:
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc; //指向大块内存
};
typedef struct {
u_char *last; //当前内存分配的结束位置,即下一段可分配内存的起始位置
u_char *end; //内存池的结束位置
ngx_pool_t *next; //连接到下一个内存池
ngx_uint_t failed; //记录内存分配失败的次数
} ngx_pool_data_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;
};
typedef struct {
ngx_fd_t fd;
u_char *name;
ngx_log_t *log;
} ngx_pool_cleanup_file_t;
//创建内存池,创建完成后如上图.
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
//内存分配函数,分配大小为size,返回指针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);
//设置max.(比max大的是大内存块.)
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;
}
//销毁内存池.内存池销毁后就完全不存在了.
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);
}
}
#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;
}
}
}
//重置内存池,重置后会内存池链表中数据区都为空,即可用.
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_t *p;
ngx_pool_large_t *l;
//释放大块内存
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}
pool->large = NULL;
//将每块pool的last指向数据区的起始位置
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
}
}
//分配内存池,从内存池链表中找到一块内存,交给用户
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);
//由于已有的内存块不够,另外开辟一块新pool
return ngx_palloc_block(pool, size);
}
//申请大块内存
return ngx_palloc_large(pool, size);
}
//和上一个函数一样,只不过没有进行对齐设置
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
if (size <= pool->max) {
p = pool->current;
do {
m = p->d.last;
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);
}
//申请一块新内存块,挂载在内存池链表中
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;
//是指pool的current为最后一个failed大于4的内存块.
/*
我们仔细看一下代码,p->d.failed++是持续的,
即后面pool中的failed肯定>=前面的failed
所以这里的"最后一个failed大于4"一定表示前面的都大于4
所以current指向第一个failed小于4的pool内存块
**/
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;
}
申请一个新的内存块,申请并挂接到内存池链表中后如图:
//分配大的内存块,直接用malloc.大块内存的管理结构存储在pool中的数据区.
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
//用malloc申请内存
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;
}
}
//没找到一个可用的大块内存管理结构,
//那么在pool池中申请一块内存用于存储该管理结构
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
//将该管理结构链入large中
//每次新申请的大块内存管理结构总是插入链表的前面.
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
解析一下上图:
1.大块内存管理结构存放在pool池中.
2.大块内存的数据区是malloc开辟出来的,不在pool池中.
3.大块内存管理结构是一个链表,将所有大块内存管理结构都连接起来.
//创建大块内存,并挂载到large链表中
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
void *p;
ngx_pool_large_t *large;
//内存对齐,创建大块内存
p = ngx_memalign(alignment, size, pool->log);
if (p == NULL) {
return NULL;
}
// 在pool的数据区创建大块内存管理结构
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
//将大块内存的管理结构挂载到large链表中
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
//释放大内存块
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;
}
//添加cleanup函数
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c;
//在pool池的数据区中创建cleanup管理结构
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
//在pool池的数据区中分配cleanup的数据
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
//将新分配的cleanup节点链入cleanup链表中
c->handler = NULL;
c->next = p->cleanup;
//注册cleanup函数
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
//关闭cleanup中的文件描述字
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
ngx_pool_cleanup_t *c;
ngx_pool_cleanup_file_t *cf;
//调用ngx_pool_cleanup_file (就是关闭文件描述字)
for (c = p->cleanup; c; c = c->next) {
if (c->handler == ngx_pool_cleanup_file) {
cf = c->data;
if (cf->fd == fd) {
c->handler(cf);
c->handler = NULL;
return;
}
}
}
}
//cleanup结构体
typedef struct {
ngx_fd_t fd; //文件描述字
u_char *name; //名称
ngx_log_t *log;
} ngx_pool_cleanup_file_t;
nginx的内存池释放
nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成.
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。每次创建一个内存池链表,每个内存池链表中所有内存池节点大小都是相等的,但是不同的链表中链表节点可能不同.也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。
这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。