Nginx 内存池

源码在此

初窥Nginx内存池

Nginx的内存池采用链表结构,每个内存池存在三个链表,每个链表以及所对应的node结构体如下所示

  • 内存池链表 : ngx_pool_t
  • 大块内存链表 : ngx_pool_large_t
  • 需特殊回收的已分配内存链表 : ngx_pool_large_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
typedef struct ngx_pool_s  ngx_pool_t;

struct ngx_pool_t { //内存池链表节点
ngx_pool_data_t d; //内存池的数据控制器
size_t max; //内存池的大小,不超过NGX_MAX_ALLOC_FROM_POOL,也就是系统页大小-1,见下文的宏定义
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;
};

typedef struct { //内存池的"数据控制器"
u_char *last; //当前内存分配结束位置,即下一段可分配内存的起始位置
u_char *end; //内存池的结束位置
ngx_pool_t *next; //链接到下一个内存池,内存池的很多块内存就是通过该指针连成链表的
ngx_uint_t failed; //记录内存分配不能满足需求的失败次数
} ngx_pool_data_t; //结构用来维护内存池的数据块,供用户分配之用。


//==================================================
//大块内存链表
typedef struct ngx_pool_large_s ngx_pool_large_t;

struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

//==================================================
//需特殊回收的已分配内存链表
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};

1
2
3
4
5
6
7
//file : src/core/ngx_palloc.h
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
#define NGX_DEFAULT_POOL_SIZE (16 * 1024)
#define NGX_POOL_ALIGNMENT 16
#define NGX_MIN_POOL_SIZE \
ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
NGX_POOL_ALIGNMENT)
  • NGX_MAX_ALLOC_FROM_POOL : 一次的最大分配量,定义是ngx_pagesize-1,而ngx_pagesize=getpagesize(),也就是系统页的大小
  • NGX_DEFAULT_POOL_SIZE : 内存池的大小,被定义为16kb
  • NGX_POOL_ALIGNMENT:对齐量为16
  • NGX_MIN_POOL_SIZE

API

创建内存池

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
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; //是否大于最大容量,如果是,则最大容量为NGX_MAX_ALLOC_FROM_POOL

p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;

return p;
}
//在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。在Linux中,这些函数返回的地址在32位系统是以8字节为边界对齐,在64位系统是以16字节为边界对齐的。有时候,对于更大的边界,例如页面,程序员需要动态的对齐。虽然动机是多种多样的,但最常见的是直接块I/O的缓存的对齐或者其它的软件对硬件的交互,因此,POSIX 1003.1d提供一个叫做posix_memalign( )的函数

//由于内存池可分配内存的大小是由NGX_MAX_ALLOC_FROM_POOL来决定的,而这个宏是由系统页的大小来决定的,因此ngx_memalign会用到posix_memalign()来进行动态对齐

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
void *p;
int err;

err = posix_memalign(&p, alignment, size);

if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"posix_memalign(%uz, %uz) failed", alignment, size);
p = NULL;
}

ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
"posix_memalign: %p:%uz @%uz", p, size, alignment);

return p;
}

销毁内存池

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
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
//遍历cleanup链表,使用所给的handler进行数据回收
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);
}
}
//遍历大块内存链表进行回收
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;
}
}
}
//推断:大块内存链表和cleanup链表只挂在头节点上

重置内存池

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

分配内存

1
2
3
4
5
6
7
8
9
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{

if (size <= pool->max) { //需要的内存小于Max
return ngx_palloc_small(pool, size, 1);
}
return ngx_palloc_large(pool, size); //大于Max
}
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
56
57
58
59
60
61
62
63
64
65
66
//需要的内存小于Max
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); //将当前指针进行对齐
//#define ngx_align_ptr(p, a) //(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
}

//如果当前内存池容量足够
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size; //分配内存

return m;
}

p = p->d.next;//查询下一个内存池

} while (p);
//遍历整个内存池链,找不到适合的,执行ngx_palloc_block
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;

//计算一个内存池的大小,然后新建一个内存池
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;
//寻找合适的位置挂载新的内存池
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;
}
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
56
57
//需要的内存大于Max
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;
//如果已经存在large链,将新分配的内存链入其中的某个节点
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
/*
// 如果当前 large 后串的 large 内存块数目大于 3 (不等于3),
// 则直接去下一步分配新内存,不再查找了
*/
if (n++ > 3) {
break;
}
}
//则分配一个节点
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) { //失败,则释放已分配内存
ngx_free(p);
return NULL;
}
//将新的large节点链入头部
large->alloc = p;
large->next = pool->large;
pool->large = large;

return p;
}

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
void *p;

p = malloc(size); //直接申请内存
if (p == NULL) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"malloc(%uz) failed", size);
}

ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

return p;
}