Nginx源码解读-内存管理

概述

Nginx在大多数涉及到内存使用的地方都用到了ngx_pool_t这个结构体以及和这个结构体相关的一些函数。这个结构体是Nginx用来管理内存的用的,涉及到内存的申请、释放以及一些相关资源的管理。当我们需要去和内存打交道的时候,不需要去花费时间在担心某块内存没有释放造成野指针的情况。

Nginx在内存管理中将内存分为两种类型,一种是小块内存,一种是大块内存。这两种内存以ngx_pool_s结构体中max作为区分界限。其中小块内存是在内存池的数据块(对应结构体中的d)中进行分配,在大部分情况下的小块内存的分配过程中,只需要去移动一个内部的指针便可。在分配大块内存的过程中,每次都会去申请一块内存。因此对比来看,小块内存的申请效率更加方便。具体内容可以参考源码中src/core/ngx_palloc.h|.c的相关内容。

数据结构定义

需释放资源

需释放资源结构体ngx_pool_cleanup_t主要管理一些需要释放的资源,比如关闭文件,删除文件等。作为ngx_pool_tcleanup链表的节点类型。

1
2
3
4
5
6
7
8
9

typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; //释放该资源对应的函数指针,有一个参数data
void *data; //对应的资源
ngx_pool_cleanup_t *next; //下一个需要释放的资源
};

大块内存

大块内存ngx_pool_large_t是Nginx的内存池中的一种内存类型,当在需要分配超过一定大小的内存时会发挥作用。

1
2
3
4
5
6
7
//大块内存结构体
typedef struct ngx_pool_large_s ngx_pool_large_t;

struct ngx_pool_large_s {
ngx_pool_large_t *next; //下一个大块内存位置
void *alloc; //内存实际位置
};

数据块

数据块ngx_pool_data_t是内存池中实际分配内存的地方,这个地方主要是分配小内存,有一点需要注意的是大块内存节点的空间(ngx_pool_large_t)也是在这里申请的。

1
2
3
4
5
6
typedef struct {
u_char *last; //可分配内存的起始位置
u_char *end; //可分配内存的结束位置
ngx_pool_t *next; //下一个内存池的位置
ngx_uint_t failed; // 当前内存池的数据块申请内存失败的次数,即当前内存池的数据块剩余可分配内存大小小于需要申请的内存大小的次数
} ngx_pool_data_t;

内存池

内存池ngx_pool_t是Nginx中用来管理内存的入口

1
2
3
4
5
6
7
8
9
10
//内存池结构体
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; //日志相关
};

相关操作

ngx_create_pool

创建一个size大小的内存池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}

/* 指向可分配内存的地址 在创建内存池时,由于要存储ngx_pool_t结构的内容 所以实际可分配的内存应该除去ngx_pool_t的大小 */
p->d.last = (u_char *) p + sizeof(ngx_pool_t);

/* 内存池可分配的最大位置指针 */
p->d.end = (u_char *) p + size;

/* 创建的时候只需要一个数据块就可以了 数据块采用链表的形式进行存储 创建的时候只需要一个数据块就可以了 因此把next设置成NULL */
p->d.next = NULL;

/* 在该数据块申请内存失败的次数 */
p->d.failed = 0;

size = size - sizeof(ngx_pool_t);

/* ngx区分小块内存和大块内存的界限,当申请的内存超过该值时会在大块内存区域去申请,否则会在小块内存区域申请 */
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;
}

ngx_destroy_pool

销毁内存池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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

/* 释放大块内存 ngx_free底层调用free */
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;
}
}
}

ngx_reset_pool

重置内存池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}

ngx_palloc

申请size大小的内存并对其

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

ngx_pnalloc

申请size大小内存不对其

1
2
3
4
5
6
7
8
9
10
11
12
/* 申请内存 不对其 */
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);
}

ngx_palloc_small

申请小块内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* 申请小块内存 */
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);
}

/* 当前数据块剩余可分配大小满足申请大小时 直接移动数据块last指针位置 */
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);
}

ngx_palloc_block

申请一个新的内存池,其实是一个数据块。因为Nginx是以链表的形式去管理内存的,无论是在小块内存还是大块内存的分配上。数据块结构体ngx_pool_data_tnext是一个ngx_pool_t类型的指针,当现有内存池的数据块剩余空间不够的时候,我们需要去创建一个新的ngx_pool_t结构体来挂在当前数据块的next下,而这个新的ngx_pool_t结构体d后面的所有字段值都是和之前内存池里面的值相同的。为了节约内存,因此在这个方法里面,我们会看到申请挂载的ngx_pool_t结构体的可分配起始位置是在申请内存返回地址的基础上加上的ngx_pool_data_t的大小,和创建的时候加上ngx_pool_t的大小不一样。具体可以参照下面的示意图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);

/* 申请一个和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_t) 在这里并非是作为一个头结点存在 因此ngx_pool_t结构体d后面的字段就用不上了,因为是和头结点是一样的 这里是为了节约内存 */
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);

/* 新内存块可分配的起始位置 */
new->d.last = m + size;

/* 如果某个数据块的申请失败次数达到了4次 那么就把内存池当前可用数据块指向下一个数据块 */
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;
}

ngx_palloc_large

申请大块内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
}

n = 0;

/* 遍历内存池大块内存管理链表 如果哪一个节点的alloc是空的就把申请的内存挂在这个节点上 如果遍历的3个节点的alloc都不为空 就在内存池的小块内存上申请一个ngx_pool_large_t大小的内存 然后在alloc指向新申请的内存 */
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;
}

ngx_pmemalign

申请大块内存并对其

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
void *p;
ngx_pool_large_t *large;

p = ngx_memalign(alignment, size, pool->log);
if (p == NULL) {
return NULL;
}

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;
}

ngx_pfree

释放内存池中的某一个大块内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

ngx_pcalloc

在内存池中申请内存并清零

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

资源相关操作

剩下的就是一些和资源相关的函数了,这类就不再一一详细的解读了。

1
2
3
4
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);