源代码路径
版本:1.8.0
src\core\Ngx_palloc.h
src\core\Ngx_palloc.c
主要作用分析
提供了一种机制,帮助进行资源管理(内存、文件)。可以类比C++
中的RAII
机制。
以内存管理为例,通常是手工进行malloc/free
,这种做法的优点是灵活、高效,缺点是容易出错,造成内存泄漏以及各种莫名其妙的BUG。
因此,在C/C++
中正确的管理内存一直是最棘手的问题。
C++
中提供了RAII
机制和智能指针来解决这个问题。Nginx
采用C
语言开发,实现了一套用来管理资源的机制。这就是nginx pool
。
数据结构
ngx_pool_data_t
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
首先看一个示意图:
last
指针表示ngx_pool_data_t
所管理的内存block
中已使用的内存的末尾地址,这也是下次分配的起始地址。
end
指针表示ngx_pool_data_t
所管理的内存block
的尾地址,该指针在分配此block
时确定,用来标记last
的边界值。
next
用来指向下一个ngx_pool_data_t
的内存block
地址,用于形成block
链表。failed
用来标记该block
使用时分配失败次数。
ngx_pool_data_t的管理和使用
围绕核心数据结构ngx_pool_data_t
, Nginx
是如何管理和使用的呢?
本篇主要从以下几个方面说明:
1) 如何初始化ngx_pool_data_t
链表;
2) 如何向链表增加ngx_pool_data_t
节点。
3) 如何回收/销毁ngx_pool_data_t
节点及链表
初始化ngx_pool_data_t
链表
基本思路:
- 所谓初始化
ngx_pool_data_t
链表就是申请一段内存block
,然后将该block
指针void* p
转成ngx_pool_data_t*
类型的指针,这样,就可以利用ngx_pool_data_t*
指针来管理这段block
,而这个block
就是ngx_pool_data_t
链表的节点。 - 当然要能够正确管理
block
还需要初始化ngx_pool_data_t
的成员变量last
、end
、next
、failed
。这样,通过ngx_pool_data_t
指针,可以获取管理ngx_pool_data_t
链表节点的能力 - 但是现在我们还没有管理整个
ngx_pool_data_t
链表的能力,那么如何做呢?通过在内存block
的起始部分添加last
、end
、next
、failed
等信息可以管理一段内存。相应地,通过在ngx_pool_data_t
链表第一个节点添加管理链表的信息,就可以管理整个链表。
同时,由于链表第一个节点只是一个特殊的节点所以,负责管理节点的结构体ngx_pool_data_t
应该是的负责管理链表结构体节点的子集
在Nginx
源码中这个管理链表的结构体就是:ngx_pool_s
,其结构体定义为:
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;
};
其中,d
是ngx_pool_data_t
用来管理节点本身,而其他变量则用来管理链表。
-
current
用来指示链表中当前正在使用的节点; -
chain
用来在节点上挂接filter
链表; -
cleanup
用来注册资源回收handler等; -
large
是与节点大内存
、小内存
有关,这里暂不分析。
通过以上分析,可知,ngx_pool_s
是管理整个ngx_pool_data_t
链表,同时也能够管理所在的链表节点。
在Nginx
源码里,申请的block
指针为void*
统一转成ngx_pool_s*
,这样,在链表第一个节点,通过ngx_pool_s*
管理整个链表及节点本身,在其他节点,通过ngx_pool_s*
管理节点本身。
根据以上思路,可以很容易明白Nginx
源码里关于创建ngx_pool_data_t
链表的代码
函数声明: Ngx_palloc.h
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
说明:
输入要分配的ngx_pool_t
节点block
大小size
,返回一个ngx_pool_t*
的指针。该指针既用来管理链表,也用来管理节点block
本身。
因此,后续链表的插入,删除都是通过操作该指针来完成,而使用链表第一个节点的block
也是通过该指针来完成。
函数定义: Ngx_palloc.c
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
// 申请内存block,为提高效率,进行了对齐
// 转成ngx_pool_t*指针
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
// 初始化ngx_pool_data_t用来管理节点block本身
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最大值为NGX_MAX_ALLOC_FROM_POOL
// 输入size最小值为sizeof(ngx_pool_t),输入小于该值会造成nginx崩溃
size = size - sizeof(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
// 初始时,链表current指向自身
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
向链表增加ngx_pool_data_t
节点
基本思路:
上一小节中关于如何初始化ngx_pool_data_t
链表的思路,可以基本套用到增加ngx_pool_data_t
节点中来,只是在结构体ngx_pool_t
成员变量初始化上有所区别。
直接上源码:
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
// 输入变量pool用来表示整个链表
u_char *m;
size_t psize;
ngx_pool_t *p, *new;
// 计算链表第一个节点的block大小
psize = (size_t) (pool->d.end - (u_char *) pool);
// 申请与链表第一个节点大小相同的内存block
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
// 转为ngx_pool_t*类型
new = (ngx_pool_t *) m;
// 初始化节点管理结构体ngx_pool_data_t
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
// 为了效率,对ngx_pool_data_t的last变量进行对齐操作
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
// 将第一个链表节点用于操作链表的pool指针的current变量指向当前节点
for (p = pool->current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}
p->d.next = new;
return m;
}
当然,在使用时,Nginx
并不是直接使用ngx_palloc_block
来操作链表,而是使用ngx_palloc
原因在于,使用ngx_create_pool
和ngx_palloc_block
构造链表之后,我们得到的是多个连续的内存块组成的列表,这些内存块大小相同,其大小为调用ngx_create_pool
传入的size
值或NGX_MAX_ALLOC_FROM_POOL
。
而在实际使用时malloc
的大小不一定等于这些内存块的大小,所以,需要提供一个接口,能够从ngx_pool_t
链表中获取需要大小的内存。
这个函数就是ngx_palloc
基本思路:
如果要获取的内存大小大于ngx_pool_t
节点的大小,那么属于创建大内存,调用ngx_palloc_large(pool, size)
。这里我们暂不分析大内存的问题。
如果要获取的内存小于ngx_pool_t
节点的大小,那么尝试从当前节点分配。
这里分两种情况:
- 当前节点剩余空间足够的情况,直接分配;
- 当前节点剩余空间不足的情况,创建一个新节点,并将新节点分配
P.S 这里的分配就是返回指针
函数声明:
void *ngx_palloc(ngx_pool_t *pool, size_t size);
函数定义:
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);
// 新建一个节点并分配
return ngx_palloc_block(pool, size);
}
// 获取内存小于链表节点内存的情况
return ngx_palloc_large(pool, size);
}
今天先到这里,后续再补充或另起一篇写。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。