Nginx 内存池

Nginx 内存池

内存池结构

内存池中主要涉及结构体:
image-20240111230140325

创建内存池

程序首先会创建一个内存池,之后程序内需要使用内存时,会从内存池中分配内存。
创建内存池的代码如下

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

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

return p;
}

创建之后的内存如下:

  • last指向可分配内存的首地址
  • end指向当前内存池的结束地址
  • next指向内存池下一个内存块的地址
  • failed记录内存分配失败次数
  • max: 最大可分配内存
  • current:分配地址时可使用的内存块
  • large:指向大块内存的管理信息(在内存池中)
  • cleanup: 清理函数链

image-20240111230131580

申请内存

Nginx申请内存通过三个API:ngx_palloc、 ngx_pcalloc、 ngx_pnalloc

  • ngx_palloc:获取内存后并不进行初始化操作
  • ngx_pcalloc: 对内存进行初始化

ngx_palloc的首地址是对齐的,而ngx_pnalloc没有考虑内存对齐

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

如果申请的内存小于等于内存池的最大可用空间,则会分配小块内存,否则分配大块内存。

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

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

如果该内存池没有这么大的内存,就会重新建立一个内存块进行分配,同时将前一个内存池的next指针指向新建的内存块上

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

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

先遍历大块内存表找到需要释放的大块内存,然后释放当前的内存块。这里释放的是内存块,由于存储内存块的链表节点属于内存池的小块内存,所以肯定不会释放掉。

释放内存池

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

//释放大块内存
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;
}
}
}