一 说明
本文简短分析nginx中内存池实现和使用场景,以讲解原理为主,忽略部分细节,nginx内存相对简陋,能挖掘东西不多,仅做个人自娱自乐,图片来自网络,如有侵权就换一张
二 内存池简要
内存池技术是一次性开辟大块内存,管理应用层对内存的统一分配和释放。能有效防止内存泄漏和内存碎片,nginx内存池相当简单,传统的链式管理的定长内存块(包括小内存快和大内存两条链表),小内存块不及时回收,在使用完毕后才统一销毁。
三 主要常量
/* 从内存池申请内存的大小分界点 */
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
四 关键数据结构
/* 内存块头部 */
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_chain_t类型的指针,扩展使用
ngx_pool_large_t *large; -- 大内存块的入口指针
ngx_pool_cleanup_t *cleanup; -- 内存池清理操作指针
ngx_log_t *log;
};
struct ngx_pool_large_s {
ngx_pool_large_t *next; -- 下一个大内存块地址
void *alloc; -- 大内存分配的内存地址
};
五 核心函数
# 1 创建内存池流程如下
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;
}
# 2 销毁内存池流程如下
void ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
// 释放大内存块链表
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;
}
}
}
# 2 申请分配内存
nginx中根据申请内存的大小分析调用以下两个函数
static 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;
// 从current开始查找有足够空间的小内存块链表
do {
m = p->d.last;
if (align) {
m = ngx_align_ptr(m, 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);
}
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;
// 将所有旧内存块标记分配失败+1,标记失败4次数后不再分配这些内存块
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;
}
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;
}
// 在大内存块链表中查看是否有被释放的内存块(只查前3个),如果有直接挂载上去
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_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) {
ngx_free(p);
return NULL;
}
// 设置头部信息,将内存块挂载在大内存块链表中头部
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
# 4 释放内存
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;
}