主要数据结构
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 {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
//以16字节对齐的方式开辟内存池的第一个块,大小size
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;
//用户可用的实际内存大小如果大于系统页大小(32位linux一般4kB),都按照最大值NGX_MAX_ALLOC_FROM_POOL处理。大于max的内存分配将特殊处理
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;
}
每个分配的内存块,end指针总是指向末尾,last指针指向剩余内存的开始部分。
分配内存
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
//申请分配的内存小于可分配最大值
if (size <= pool->max) {
//从current块开始往后查找可用块
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);
}
//所申请的内存大于max,需要申请大块内存
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;
//计算一个ngx_pool_t的大小
psize = (size_t) (pool->d.end - (u_char *) pool);
//采用内存对齐的方式分配一块buff,win32系统默认malloc采用8字节对齐,这里采用16字节对齐。
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;
//除了第一个内存块会有current、max等成员,之后的所有内存块只有ngx_pool_data_t和用户内存两部分。
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
current = pool->current;
//从current块开始依次向后校验,因为走到这一步表示前面的块剩余空间都不够用,需要将failed字段加1,同时将current指针移动到failed小于4的块
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
current = p->d.next;
}
}
//将新块添加到pool的末尾
p->d.next = new;
//如果current未NULL,则指向new
pool->current = current ? current : new;
return m;
}
内存池中的内存块默认大小为pagesize-1,即4095Bytes。使用内存池,主要用于存储空间较小、数量较多的结构体,可以避免产生内存碎片,提升程序执行性能。对于大块的内存申请,则完全不需要使用内存池进行分配。nginx中,对于超过4095Bytes的内存,直接malloc。同时为了便于管理,会在内存池中开辟一个ngx_pool_large_t结构体,管理对应的内存块。
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
//直接malloc一块size大小的内存,size>pagesize
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
//查找前3个large对象有无可用的挂载点,有则直接将内存挂载,否则重新分配一个large对象
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 3) {
break;
}
}
//分配新的large对象
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;
}
内存池销毁
内存池在销毁前,会进行一些清理工作。比如清理ssl,image,mail等等操作。这些都是在销毁内存池的时候进行的。
主要结构体如下:
typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; //清理函数句柄
void *data; //清理函数需要使用到的数据信息
ngx_pool_cleanup_t *next; //指向下一个结构体
};
调用ngx_pool_cleanup_add添加清理任务。
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c;
//从内存池分配空间
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
//初始化后用头插法加入链表
c->handler = NULL;
c->next = p->cleanup;
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
业务模块在调用ngx_pool_cleanup_add后,可以设定清理函数句柄和参数内容。在连接结束后,调用清理函数,释放内存池资源。
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);
}
}
//释放large内存
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;
}
}
}
内存池的好处
nginx作为一个web服务器,一切都是为性能服务的,内存池也不例外。内存池的好处主要有:
- 开辟内存池的方式,用来分配比较小的内存,比如各个结构体。这样可以避免出现内存碎片,而影响运行效率。
- 尽量的从内存池里分配空间,可以减少malloc的次数。因为malloc本身由glibc分配内存,glibc为了防止竞争会加锁,过于频繁调用malloc也会影响性能。