先从一个简单的内存池(Nginx内存池,nginx-1.6.1版本)实现开始,来获取其大致的概念。一个内存池可以认为是一个内存分配器,有些内存池被设计成只能分配固定长度的内存块,而有些则设计成可分配不定长度的内存块。另一个特点是内存块的释放,在内存池被销毁后,从该内存池的分配的所有内存块都将会被销毁,可以称之为“一键销毁”功能!
接口设计:
Nginx内存池的核心接口,可以分成两大类(声明于ngx_palloc.h头文件中):
1、内存池操作接口
2、基于内存池的内存操作接口
3、接口的使用范式如下:
从接口的设计来看,一切还算比较明朗,但以下几点还是需要注意:
1) 从ngx_palloc()等内存分配函数的size参数可以看出,基于内存池的内存分配操作不是固定长度的。
2) 内存分配函数考虑了一些两种情况,内存初始化,以及内存地址对齐,因此内存分配操作可以按以下方式分类:
|
内存是否初始化 |
内存地址是否对齐 |
ngx_palloc |
否 |
按内部默认方式对齐 |
ngx_pnalloc |
否 |
未对齐 |
ngx_pcalloc |
初始化为0 |
按内部默认方式对齐 |
ngx_pmemalign |
否 |
按用户指定方式对齐 |
3) 基于第1点,我们有理由相信ngx_create_pool()的size参数应该是限制该内存池所能分配的内存块大小的上限。但其实不然,后面关于实现的分析可以看到这点。
4) 在内存池生命周期内,可以通过ngx_free()归还内存块,但其实并不是所有从内存池分配的内存都可以通过该接口来释放的。
5)支持ngx_reset_pool()重置内存池操作
设计与实现:
在Nginx内存池的设计中,其所管理的内存有两种类型,block和large memory。
block是一个内存池每次从系统分配的固定长度的内存块(一般通过malloc分配),block的长度在创建内存池就已经决定(即由ngx_create_pool的size参数所决定),且对于一个内存池而言所有block的长度都是一致的。前面所讨论的ngx_create_pool函数的size参数表示的是,内存池内部实现中一个block的大小。一个block内存有三个方面的用处:1).用于block自身的管理,2).用于large memory的管理(只有需要分配large内存时才存在),3).分配给用户。从接口设计的角度讲,这个参数设计得并不好,因为它需要使用者了解内存池的内部实现,才能明白其准确意义!
large memory则指的是长度超过block限制大小或者是按用户指定内存地址对齐方式分配(即ngx_pmemalign函数)的内存块。
以上两种类型的内存在Nginx内存池中,各自通过链表管理起来。
Nginx内存池:
block:
large memory:
1.内存池管理函数的实现比较简单
点击(此处)折叠或打开
-
/**创建内存池*/
-
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log)
-
{
-
ngx_pool_t *p;
-
-
/*分配一个block(大小为size)的内存*/
-
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
-
if (p == NULL) {
-
return NULL;
-
}
-
-
/*初始化block管理数据
-
*这是该内存池的第一个block,除了要一部分内存用于管理该block外,
-
*还有一部分内存被用于管理内存池本身
-
*由于ngx_pool_t 的定义是内嵌一个ngx_pool_data_t ,
-
*因此总共用于管理block和内存池的数据长度就是sizeof(ngx_pool_t)*/
-
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;
-
-
/*设置该内存池能从block分配的内存块的大小限制*/
-
size = size - sizeof(ngx_pool_t);
-
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
-
-
/*初始化内存池管理数据*/
-
p->current = p;//从第一个block开始查询链表,即查询链表头
-
p->chain = NULL;
-
p->large = NULL;
-
p->cleanup = NULL;
-
p->log = log;
-
-
return p;
- }
点击(此处)折叠或打开
-
/**销毁内存池*/
-
void ngx_destroy_pool(ngx_pool_t *pool)
- {
- ………
-
/*遍历large内存链表释放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);
-
}
-
}
-
………
-
/*遍历block链表, 释放block内存*/
-
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
-
ngx_free(p);
-
-
if (n == NULL) {
-
break;
-
}
-
}
-
/*注意以上两个for循环的次序不能颠倒,因为large内存的管理数据位于block中*/
- }
点击(此处)折叠或打开
-
/**重置内存池*/
-
void ngx_reset_pool(ngx_pool_t *pool)
-
{
-
ngx_pool_t *p;
-
ngx_pool_large_t *l;
-
-
/*遍历large链表释放内存给系统*/
-
for (l = pool->large; l; l = l->next) {
-
if (l->alloc) {
-
ngx_free(l->alloc);
-
}
-
}
-
-
/*遍历block链表, 但不释放内存给系统, 仅将block管理数据重置*/
-
for (p = pool; p; p = p->d.next) {
-
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
-
p->d.failed = 0;
-
}
-
-
/*重置查询表头*/
-
pool->current = pool;
-
pool->chain = NULL;
-
pool->large = NULL;
- }
2.内存分配,我们从ngx_palloc开始分析,先看流程图
点击(此处)折叠或打开
-
/**分配内存,返回内存地址按照某种方式对齐,内存未作任何初始化*/
-
void *ngx_palloc(ngx_pool_t *pool, size_t size)
-
{
-
u_char *m;
-
ngx_pool_t *p;
-
-
/*判断分配大小是否超出block现在*/
-
if (size <= pool->max) {
-
/*大小没有超出block限制*/
-
p = pool->current;
-
/*dowhile 循环, 尝试从现存block中分配内存*/
-
do {
-
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//内存地址按NGX_ALIGNMENT对齐处理
-
if ((size_t) (p->d.end - m) >= size) {
-
p->d.last = m + size;
-
return m;
-
}
-
p = p->d.next;
-
} while (p);
-
-
/*现存block无法满足分配需求,创建一个新的block用于内存分配*/
-
return ngx_palloc_block(pool, size);
-
}
-
-
/*大小超出block限制,分配large内存块*/
-
return ngx_palloc_large(pool, size);
- }
点击(此处)折叠或打开
-
/**创建一个新的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;
-
-
/*计算block大小, 注意对于一个内存池而言,所有block的大小都是一致的.
-
*为什么ngx_pool_t不维护这么一个域,而每次都去动态计算呢?*/
-
psize = (size_t) (pool->d.end - (u_char *) pool);
-
-
/*从系统中分配一个block, 并初始化block管理数据,注意管理数据所需的内存也来自block*/
-
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;
-
/*注意第一个block已经在ngx_create_pool中创建了,所有本函数创建的block均由ngx_pool_data_t 管
-
* 理,因此此处偏移sizeof(ngx_pool_data_t)就ok,没有必要偏移sizeof(ngx_pool_t)*/
-
m += sizeof(ngx_pool_data_t);
-
m = ngx_align_ptr(m, NGX_ALIGNMENT);//内存地址,按NGX_ALIGNMENT方式对齐
-
new->d.last = m + size; //设置有效内存起始位置
-
-
/*更新查询链表头,跳过所有内存分配次数大于4(为什么是4?)的block*/
-
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;//将新建的block加入block链表
-
-
pool->current = current ? current : new;
-
return m;
- }
点击(此处)折叠或打开
-
/**分配large内存*/
-
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)
-
{
-
void *p;
-
ngx_uint_t n;
-
ngx_pool_large_t *large;
-
-
/*从系统分配large内存块*/
-
p = ngx_alloc(size, pool->log);
-
if (p == NULL) {
-
return NULL;
-
}
-
-
/*尝试寻找large内存链表中无效的管理数据,更新该管理数据,
-
*将新分配的large内存块直接加入large内存链表*/
-
n = 0;
-
for (large = pool->large; large; large = large->next) {
-
if (large->alloc == NULL) {
-
large->alloc = p;
-
return p;
-
}
-
if (n++ > 3) {//为啥是3?
-
break;
-
}
-
}
-
-
/*需要从block中重新分配一个块内存用于管理新分配的large内存块
-
*注意这里调用了ngx_palloc, 这是一个隐藏比较深的递归调用*/
-
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;
-
-
/*对比ngx_palloc_large 与ngx_palloc_block ,发现,block的管理数据从block自身分出一般内存来使用的,*而large内存块的管理数据,则不在large内存块中,需另行分配(其实就从block中分配)*/
-
return p;
- }
ngx_pnalloc的实现除了不对内存地址做对齐处理外,其他与ngx_pnalloc一致;ngx_pcalloc的实现是基于ngx_pnalloc分配内存,然后,将内存块清0。接下来是ngx_pmemalign。
点击(此处)折叠或打开
-
/**分配内存,返回内存地址按alignment对齐,内存未作任何初始化*/
-
void * ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
-
{
-
void *p;
-
ngx_pool_large_t *large;
-
-
/*用户指定alignment对齐要求,直接从系统中分配large内存块*/
-
p = ngx_memalign(alignment, size, pool->log);
-
if (p == NULL) {
-
return NULL;
-
}
-
-
/*直接从block中分配管理large内存块的内存,
-
* 为什么不像ngx_palloc_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;
- }
点击(此处)折叠或打开
-
/**释放内存*/
-
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
-
{
-
ngx_pool_large_t *l;
-
-
/*遍历large链表,释放large内存块,
-
*注意此处并没有释放管理large内存块的内存(即large链表节点),
-
*而仅是将其alloc指针设为NULL*/
-
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;
- }
最后以一个内存池快照作总结: