当从内存池中存取数据的时候,nginx是分为两种类型来处理的,一种是小块数据,他是直接从内存块中取得数据,另一种是当为大数据块的时候,直接malloc一个内存块(内存池外部分配数据),然后保存这个指针到内存池。
Nginx内存池的主体结构
内存池的头部结构
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_data_t d 数据区指针包含我们所需要操作的这个内存池的数据的指针
last表示已经使用数据的结尾,end标识当前内存池的结尾,也就是说end-last就是当前内存池未使用的大小
通常我们知道,在普通内存池下,我们去请求内存,如果这时候内存满了,就会扩大内存池。nginx则不会这么做,他会直接分配一个内存池,然后链接到data的next指针。在nginx里,每个内存池都拥有自己的子内存池,我们请求的时候就会遍历这些内存池。
failed 是为了标记我们请求内存(小内存块)由于内存池空间不足,我们需要重新分配一个子内存池的次数。
内存池的基本操作
创建内存池 | ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log); |
销毁内存池 | void ngx_destroy_pool(ngx_pool_t *pool); |
重置内存池 | void ngx_reset_pool(ngx_pool_t *pool); |
内存申请(对齐) | void * ngx_palloc(ngx_pool_t *pool, size_t size); |
内存申请(不对齐) | void * ngx_pnalloc(ngx_pool_t *pool, size_t size); |
内存清除 | ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p); |
创建内存池
///内存池的数据区的最大容量。
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
///可以看到直接分配size大小,也就是说我们只能使用size-sizeof(ngx_poll_t)大小
p = ngx_alloc(size, log);
if (p == NULL)
{
return NULL;
}
///开始初始化数据区。
///由于一开始数据区为空,因此last指向数据区的开始。
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
///end也就是数据区的结束位置
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;
///这里才是我们真正能使用的大小。
size = size - sizeof(ngx_pool_t);
///然后设置max。内存池的最大值也就是size和最大容量之间的最小值。
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
///current表示当前的内存池。
p->current = p;
///其他的域置NULL。
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
///返回指针。
return p;
}
内存申请
在nginx中有3个函数可用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配会对齐,第二个函数用来分配一块清0的内存。第三个函数分配的内存不会对齐。
我们只分析第一个函数。
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
///首先判断当前申请的大小是否超过max,如果超过则说明是大块,此时进入large
if (size <= pool->max)
{
///得到当前的内存池指针。
p = pool->current;
///开始遍历内存池,
do {
///首先对齐last指针。
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
///然后得到当前内存池中的可用大小。如果大于请求大小,则直接返回当前的last,也就是数据的指针。
if ((size_t) (p->d.end - m) >= size)
{
///更新last,然后返回前面保存的last。
p->d.last = m + size;
return m;
}
///否则继续遍历
p = p->d.next;
} while (p);
///到达这里说明内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。(紧接着我们会分析
return ngx_palloc_block(pool, size);
}
///申请大块。
return ngx_palloc_large(pool, size);
}
我们再来看看 ngx_palloc_block函数的实现,这个函数就是重新分配一块内存池,然后链接到当前内存池的数据域指针。
重新new的这个内存池和父内存池大小是一样的。
并且新得到的这个内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下
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;//设置新内存块的end
new->d.next = NULL;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t);//将指针m移动到d后面的一个位置,作为起始位置
m = ngx_align_ptr(m, NGX_ALIGNMENT);//对m指针按4字节对齐处理
new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存
current = pool->current;
//这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,
//就忽略,不需要每次都从头找起
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;
}
ngx_palloc_large(ngx_pool_t *pool, size_t size)
在ngx_palloc中首先会判断申请的内存大小是否超过内存块的最大限值,如果超过,则直接调用ngx_palloc_large,进入大内存块的分配流程;
1:static void *
2: ngx_palloc_large(ngx_pool_t *pool, size_t size)
3: {
4: void *p;
5: ngx_uint_t n;
6: ngx_pool_large_t *large;
7: // 直接在系统堆中分配一块空间
8: p = ngx_alloc(size, pool->log);
9: if (p == NULL) {
10: return NULL;
11: }
12:
13: n = 0;
14: // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理
15: for (large = pool->large; large; large = large->next) {
16: if (large->alloc == NULL) {
17: large->alloc = p;
18: return p;
19: }
20:
21: if (n++ > 3) {
22: break;
23: }
24: }
25: //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
26: large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
27: if (large == NULL) {
28: ngx_free(p);
29: return NULL;
30: }
31:
32: large->alloc = p;
33: large->next = pool->large;
34: pool->large = large;
35:
36: return p;
37: }
分配完了,我们来看释放,在nginx中,只有大内存提供释放的接口,可以供我们手工释放,但是小内存没有释放的接口,只有当整个内存池被销毁,才会被释放。
1:ngx_int_t
2: ngx_pfree(ngx_pool_t *pool, void *p)
3: {
4: ngx_pool_large_t *l;
5: //只检查是否是大内存块,如果是大内存块则释放
6: for (l = pool->large; l; l = l->next) {
7: if (p == l->alloc) {
8: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
9: "free: %p", l->alloc);
10: ngx_free(l->alloc);
11: l->alloc = NULL;
12:
13: return NGX_OK;
14: }
15: }
16:
17: return NGX_DECLINED;
18: }
ngx_destroy_pool
ngx_destroy_pool这个函数用于销毁一个内存池:
void
2: ngx_destroy_pool(ngx_pool_t *pool)
3: {
4: ngx_pool_t *p, *n;
5: ngx_pool_large_t *l;
6: ngx_pool_cleanup_t *c;
7:
8: //首先调用所有的数据清理函数
9: for (c = pool->cleanup; c; c = c->next) {
10: if (c->handler) {
11: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
12: "run cleanup: %p", c);
13: c->handler(c->data);
14: }
15: }
16:
17: //释放所有的大块内存
18: for (l = pool->large; l; l = l->next) {
19:
20: ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
21:
22: if (l->alloc) {
23: ngx_free(l->alloc);
24: }
25: }
26:
27: //最后释放所有内存池中的内存块
28: for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
29: ngx_free(p);
30:
31: if (n == NULL) {
32: break;
33: }
34: }
35: }