Nginx作为高性能的web服务器,它是如何管理内存的呢?
内存池的优点
- pool化处理,避免多次向系统申请内存,减少用户态和内核态切换,提高内存分配效率;
- 内存统一分配和回收,避免内存泄漏;
- 整块分配,避免内存过于碎片化;
- 内存对齐处理,减少CPU读取内存次数,提高寻址效率;
内存池相关的数据结构
结构体示意图

数据结构定义
typedef struct ngx_pool_s ngx_pool_t;
typedef struct ngx_pool_large_s ngx_pool_large_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; //内存块大于max内存块的链表
ngx_pool_cleanup_t *cleanup; //销毁内存池回调函数
ngx_log_t *log; //错误日志指针
};
typedef struct {
u_char *last; //当前内存块已使用到此处,下一次使用从此处开始
u_char *end; //内存块结束位置
ngx_pool_t *next; //内存池有很多内存块,通过next指针组成链表
ngx_uint_t failed; //内存块分配失败次数
} ngx_pool_data_t; //小内存块(链表)
//大内存块(链表)
struct ngx_pool_large_s {
ngx_pool_large_t *next; //下一数据块地址
void *alloc; //当前数据块地址
};
内存池主要函数
新建内存池
/*
* 新建一个内存池
*/
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);
//max最大为pagesize-1(比如:x86时为4095), 即:无论小块内存空间是否足够,超过pagesize-1大小的内存均走large块分配
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;
}
从内存池申请内存
- 先对齐再分配内存
/*
* 从内存池中申请一块内存
* (1)如果申请的内存小于定义的max,则从内存池中分配内存,需先对齐再分配
* (2)否则分配大块内存挂载到large指针上
*/
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 1); //小块内存(先对齐)
}
#endif
return ngx_palloc_large(pool, size);
}
- 先对齐再分配内存, 并填充0
/*
* 从内存池中申请内存,并填充0
*/
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
void *p;
p = ngx_palloc(pool, size);
if (p) {
ngx_memzero(p, size);
}
return p;
}
- 不用对齐分配内存
/*
* 从内存池中申请一块内存 (小块内存无需先对齐)
*/
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 0); //小块内存(无需先对齐)
}
#endif
return ngx_palloc_large(pool, size);
}
从内存池申请内存小块内存
/*
* 分配小块内存
* 1. 循环遍历内存块ngx_pool_data_t链表
* 2. 如果当前内存块剩余大小满足申请的内存大小,则移动相应指针,返回分配的内存指针
* 3. 否则,移动到下一个内存块
* 4. 如果所有内存块都没能满足申请的内存大小,则重新申请一块内存块挂载到链表上,并返回分配的内存地址
*/
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
u_char *m;
ngx_pool_t *p;
p = pool->current;
do {
m = p->d.last; //最后使用位置
if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT); //先内存对齐, 减少CPU读取内存次数
}
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);
}
新建小内存块再分配
/*
* 再次申请小块内存,并挂载到小块内存链表上,并返回当次申请的内存指针
*/
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new;
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;
for (p = pool->current; p->d.next; p = p->d.next) {
//调整各个内存块分配失败次数,如果失败次数大于4,则此块内存节点不再使用
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}
p->d.next = new;
return m;
}
分配大内存
/*
* 分配大内存块
*/
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); //向系统申请size大小堆内存
if (p == NULL) {
return NULL;
}
n = 0;
//查找一个空的large区,如果有,则将分配的空间p交由它管理(最多3次)
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) { //ngx_pfree函数会产生空的large区
large->alloc = p;
return p;
}
if (n++ > 3) { //超过一定次数,放弃查找空的large区
break;
}
}
//创建一个新的large节点
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) {
ngx_free(p);
return NULL;
}
//新分配的空间p交由新建的large节点管理,并且插入到large链表头部
large->alloc = p;
large->next = pool->large;
pool->large = large;
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);
}
}
#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/
for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
}
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 (l = pool->large; l; l = l->next) {
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. 释放大块内存
* 2. 重置可开始分配位置指针、分配失败次数为0、可用小内存块current指针指向首个内存块
*/
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);
}
}
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //恢复至初始为位置
p->d.failed = 0; //分配失败次数重置为0
}
pool->current = pool; //current指向首个内存块
pool->chain = NULL;
pool->large = NULL;
}
回收大块内存链表上的某个节点内存
/*
* 回收大块内存链表上的某个节点内存
*/
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;
}