内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。
内存池概念一定程度上符合 “计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决“ 的设计理念,内存池避免出现内存碎片,减少向操作系统申请内存的次数,降低各个模块的开发复杂度。
nginx内存池综述
nginx设计了一种简单的内存池,即不负责回收内存池中已分配的内存,只在该内存池对应使用区域的生命周期结束时才销毁内存池,并把内存一次性归还给操作系统。
这样的优点在于大大减少了频繁的malloc
与free
,降低了CPU资源消耗,也减少了了内存碎片。但是也引入了一个新问题:即如果使用内存池的区域生命周期很长,但分配的每一块内存使用的生命周期很短,那么会导致内存不能及时回收,造成内存浪费。所有一般应用中不会这么设计内存池,而nginx严格明确生命周期,同时在设计上考虑了大小块内存不同分配策略,所有该问题在nginx中没有影响。
ngx_pool_t结构体
nginx内存池结构定义在src/core/ngx_palloc.h
文件,与内存池设计相关的文件还有src/core/ngx_palloc.c
,os/unix/alloc.c
,os/unix/alloc.h
文件。
nginx内存池在设计上区分大小块内存,通常小于等于NGX_MAX_ALLOC_FROM_POOL
大小意味着小块内存,但不是绝对,当调用ngx_create_pool
创建内存,size
参数小于NGX_MAX_ALLOC_FROM_POOL+sizeof(ngx_pool_t)
时,则size-sizeof(ngx_pool_t)
字节就是小块内存标准。
1 | /* ngx_pagesize在main函数调用ngx_os_init函数时初始化 */ |
2 |
|
小块内存,通过从进程的堆中预分配更多的内存,而后直接使用这块内存的一部分作为小块内存返回给申请者,以此实现减少碎片和调用malloc的次数。当内存池预分配的size
不足使用时,就会再接着分配一个小块内存池,用ngx_pool_data_t
的next
成员相连。新增的ngx_pool_t
结构体中与小块内存无关的其他成员此时是无意义的,例如max
不会赋值,lagre
链表为空等。
1 | typedef struct ngx_pool_large_s ngx_pool_large_t; |
2 | |
3 | struct ngx_pool_large_s { |
4 | ngx_pool_large_t *next; /* 所有大块内存通过next指针联起来 */ |
5 | void *alloc; /* 数据域 */ |
6 | }; |
7 | |
8 | typedef struct { |
9 | u_char *last; /* 指向未分配的空闲内存的首地址 */ |
10 | u_char *end; /* 指向当前小块内存池的尾部 */ |
11 | ngx_pool_t *next; /* 同属一个pool的多个小块内存间,通过next相连 */ |
12 | /* 当剩余空间不足以分配小块内存时,failed成员就会+1,当failed成员大于4 |
13 | * 后,ngx_pool_t的current将移向下一个小块内存池 */ |
14 | ngx_uint_t failed; |
15 | } ngx_pool_data_t; |
16 | |
17 | typedef struct ngx_pool_s ngx_pool_t; |
18 | |
19 | struct ngx_pool_s { |
20 | /* 小块内存相关结构,当分配小块内存时,剩余的预分配空间不足时,会在分配一个 |
21 | * ngx_pool_t,会在d中的next成员构成单链表 */ |
22 | ngx_pool_data_t d; |
23 | size_t max; /* 评估内存属于小块内存还是大块内存的标准 */ |
24 | /* 多个小块内存池构成链表时,current指向分配内存时遍历的第一个小块内存池 */ |
25 | ngx_pool_t *current; |
26 | ngx_chain_t *chain; /* 与内存池关系不大,略过 */ |
27 | /* 大块内存堆分配,这里组成单向链表,便于销毁时,同时释放 */ |
28 | ngx_pool_large_t *large; |
29 | /* 所有待清理资源(例如需要关闭或删除的文件等)组成单链表 */ |
30 | ngx_pool_cleanup_t *cleanup; |
31 | ngx_log_t *log; /* 日志 */ |
32 | }; |
ngx_pool_cleanup_t结构体
ngx_pool_t
不止希望程序员不用释放内存,而且还能不需要释放如文件等资源。使用ngx_pool_cleanup_add
方式添加清理方法。
1 | typedef void (*ngx_pool_cleanup_pt)(void *data); |
2 | |
3 | typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t; |
4 | |
5 | struct ngx_pool_cleanup_s { |
6 | ngx_pool_cleanup_pt handler; /* 初始化为NULL,需要设置清理方法 */ |
7 | void *data; /* 清理方法对应的参数 */ |
8 | ngx_pool_cleanup_t *next; /* 多个方法组成链表 */ |
9 | }; |
内存池相关方法
内存池操作
创建内存池,size
大小包含了sizeof(ngx_pool_t)
的管理结构大小,通常可以设置size
为NGX_DEFAULT_POOL_SIZE
。
1 | ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log) |
2 | { |
3 | ngx_pool_t *p; |
4 | |
5 | p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); |
6 | if (p == NULL) { |
7 | return NULL; |
8 | } |
9 | |
10 | p->d.last = (u_char *) p + sizeof(ngx_pool_t); |
11 | p->d.end = (u_char *) p + size; |
12 | p->d.next = NULL; |
13 | p->d.failed = 0; |
14 | |
15 | size = size - sizeof(ngx_pool_t); |
16 | p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; |
17 | |
18 | p->current = p; |
19 | p->chain = NULL; |
20 | p->large = NULL; |
21 | p->cleanup = NULL; |
22 | p->log = log; |
23 | |
24 | return p; |
25 | } |
销毁内存池,同时会把通过该pool分配出的内存释放,还会执行通过ngx_pool_cleanup_add
方法添加的各类资源清理方法。
1 | void ngx_destroy_pool(ngx_pool_t *pool) |
2 | { |
3 | ngx_pool_t *p, *n; |
4 | ngx_pool_large_t *l; |
5 | ngx_pool_cleanup_t *c; |
6 | |
7 | for (c = pool->cleanup; c; c = c->next) { |
8 | if (c->handler) { |
9 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, |
10 | "run cleanup: %p", c); |
11 | c->handler(c->data); |
12 | } |
13 | } |
14 | |
15 | for (l = pool->large; l; l = l->next) { |
16 | |
17 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); |
18 | |
19 | if (l->alloc) { |
20 | ngx_free(l->alloc); |
21 | } |
22 | } |
23 | |
24 | for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { |
25 | ngx_free(p); |
26 | |
27 | if (n == NULL) { |
28 | break; |
29 | } |
30 | } |
31 | } |
重置内存池,即将内存池中的原有内存释放后继续使用。该实现会把大块内存释放给操作系统,小块内存不释放。
1 | void ngx_reset_pool(ngx_pool_t *pool) |
2 | { |
3 | ngx_pool_t *p; |
4 | ngx_pool_large_t *l; |
5 | |
6 | for (l = pool->large; l; l = l->next) { |
7 | if (l->alloc) { |
8 | ngx_free(l->alloc); |
9 | } |
10 | } |
11 | |
12 | for (p = pool; p; p = p->d.next) { |
13 | p->d.last = (u_char *) p + sizeof(ngx_pool_t); |
14 | p->d.failed = 0; |
15 | } |
16 | |
17 | pool->current = pool; |
18 | pool->chain = NULL; |
19 | pool->large = NULL; |
20 | } |
内存池分配,释放操作
分配地址对齐的内存。按总线长度(例如sizeof(unsigned long))对齐地址后,可以减少CPU读取内存的次数,当然代价是一些内存浪费。
1 | void *ngx_palloc(ngx_pool_t *pool, size_t size) |
2 | { |
3 | u_char *m; |
4 | ngx_pool_t *p; |
5 | |
6 | if (size <= pool->max) { |
7 | |
8 | p = pool->current; |
9 | |
10 | do { |
11 | m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); |
12 | if ((size_t) (p->d.end - m) >= size) { |
13 | p->d.last = m + size; |
14 | |
15 | return m; |
16 | } |
17 | |
18 | p = p->d.next; |
19 | |
20 | } while (p); |
21 | |
22 | return ngx_palloc_block(pool, size); |
23 | } |
24 | |
25 | return ngx_palloc_large(pool, size); |
26 | } |
分配内存时,不进行地址对齐操作。
1 | void *ngx_pnalloc(ngx_pool_t *pool, size_t size) |
2 | { |
3 | u_char *m; |
4 | ngx_pool_t *p; |
5 | |
6 | if (size <= pool->max) { |
7 | |
8 | p = pool->current; |
9 | |
10 | do { |
11 | m = p->d.last; |
12 | |
13 | if ((size_t) (p->d.end - m) >= size) { |
14 | p->d.last = m + size; |
15 | |
16 | return m; |
17 | } |
18 | |
19 | p = p->d.next; |
20 | |
21 | } while (p); |
22 | |
23 | return ngx_palloc_block(pool, size); |
24 | } |
25 | |
26 | return ngx_palloc_large(pool, size); |
27 | } |
分配出地址对齐的内存后,在调用memset
将这些内存全部清0。
1 | void* ngx_pcalloc(ngx_pool_t *pool, size_t size) |
2 | { |
3 | void *p; |
4 | |
5 | p = ngx_palloc(pool, size); |
6 | if (p) { |
7 | ngx_memzero(p, size); |
8 | } |
9 | |
10 | return p; |
11 | } |
如果pool空间已经用完,则从新开辟空间ngx_pool_t。
1 | static void * ngx_palloc_block(ngx_pool_t *pool, size_t size) |
2 | { |
3 | u_char *m; |
4 | size_t psize; |
5 | ngx_pool_t *p, *new; |
6 | |
7 | psize = (size_t) (pool->d.end - (u_char *) pool); |
8 | |
9 | m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); |
10 | if (m == NULL) { |
11 | return NULL; |
12 | } |
13 | |
14 | new = (ngx_pool_t *) m; |
15 | |
16 | new->d.end = m + psize; |
17 | new->d.next = NULL; |
18 | new->d.failed = 0; |
19 | |
20 | m += sizeof(ngx_pool_data_t); |
21 | m = ngx_align_ptr(m, NGX_ALIGNMENT); |
22 | new->d.last = m + size; |
23 | |
24 | for (p = pool->current; p->d.next; p = p->d.next) { |
25 | if (p->d.failed++ > 4) { |
26 | pool->current = p->d.next; |
27 | } |
28 | } |
29 | |
30 | p->d.next = new; |
31 | |
32 | return m; |
33 | } |
大块内存分配。
1 | static void * ngx_palloc_large(ngx_pool_t *pool, size_t size) |
2 | { |
3 | void *p; |
4 | ngx_uint_t n; |
5 | ngx_pool_large_t *large; |
6 | |
7 | p = ngx_alloc(size, pool->log); |
8 | if (p == NULL) { |
9 | return NULL; |
10 | } |
11 | |
12 | n = 0; |
13 | |
14 | for (large = pool->large; large; large = large->next) { |
15 | if (large->alloc == NULL) { |
16 | large->alloc = p; |
17 | return p; |
18 | } |
19 | |
20 | if (n++ > 3) { |
21 | break; |
22 | } |
23 | } |
24 | |
25 | large = ngx_palloc(pool, sizeof(ngx_pool_large_t)); |
26 | if (large == NULL) { |
27 | ngx_free(p); |
28 | return NULL; |
29 | } |
30 | |
31 | large->alloc = p; |
32 | large->next = pool->large; |
33 | pool->large = large; |
34 | |
35 | return p; |
36 | } |
按参数alignment进行地址对齐来分配内存,这样分配出的内存不管申请的size有多小,都是不会使用小块内存池,会从进程的堆中分配内存,并挂在大块内存组成的large链表中。
1 | void * ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment) |
2 | { |
3 | void *p; |
4 | ngx_pool_large_t *large; |
5 | |
6 | p = ngx_memalign(alignment, size, pool->log); |
7 | if (p == NULL) { |
8 | return NULL; |
9 | } |
10 | |
11 | large = ngx_palloc(pool, sizeof(ngx_pool_large_t)); |
12 | if (large == NULL) { |
13 | ngx_free(p); |
14 | return NULL; |
15 | } |
16 | |
17 | large->alloc = p; |
18 | large->next = pool->large; |
19 | pool->large = large; |
20 | |
21 | return p; |
22 | } |
提前释放大块内存,它的效率不高。
1 | ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) |
2 | { |
3 | ngx_pool_large_t *l; |
4 | |
5 | for (l = pool->large; l; l = l->next) { |
6 | if (p == l->alloc) { |
7 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, |
8 | "free: %p", l->alloc); |
9 | ngx_free(l->alloc); |
10 | l->alloc = NULL; |
11 | |
12 | return NGX_OK; |
13 | } |
14 | } |
15 | |
16 | return NGX_DECLINED; |
17 | } |
内存池同步操作
添加一个需要在内存池释放时的同步释放的资源。
1 | ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) |
2 | { |
3 | ngx_pool_cleanup_t *c; |
4 | |
5 | c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); |
6 | if (c == NULL) { |
7 | return NULL; |
8 | } |
9 | |
10 | if (size) { |
11 | c->data = ngx_palloc(p, size); |
12 | if (c->data == NULL) { |
13 | return NULL; |
14 | } |
15 | |
16 | } else { |
17 | c->data = NULL; |
18 | } |
19 | |
20 | c->handler = NULL; |
21 | c->next = p->cleanup; |
22 | |
23 | p->cleanup = c; |
24 | |
25 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); |
26 | |
27 | return c; |
28 | } |
在内存池释放前,如果需要提前关闭文件,则调用该方法。
1 | void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd) |
2 | { |
3 | ngx_pool_cleanup_t *c; |
4 | ngx_pool_cleanup_file_t *cf; |
5 | |
6 | for (c = p->cleanup; c; c = c->next) { |
7 | if (c->handler == ngx_pool_cleanup_file) { |
8 | |
9 | cf = c->data; |
10 | |
11 | if (cf->fd == fd) { |
12 | c->handler(cf); |
13 | c->handler = NULL; |
14 | return; |
15 | } |
16 | } |
17 | } |
18 | } |
以关闭文件来释放资源的方法。
1 | void ngx_pool_cleanup_file(void *data) |
2 | { |
3 | ngx_pool_cleanup_file_t *c = data; |
4 | |
5 | ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d", |
6 | c->fd); |
7 | |
8 | if (ngx_close_file(c->fd) == NGX_FILE_ERROR) { |
9 | ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, |
10 | ngx_close_file_n " \"%s\" failed", c->name); |
11 | } |
12 | } |
以删除文件来释放资源的方法。
1 | void ngx_pool_delete_file(void *data) |
2 | { |
3 | ngx_pool_cleanup_file_t *c = data; |
4 | |
5 | ngx_err_t err; |
6 | |
7 | ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s", |
8 | c->fd, c->name); |
9 | |
10 | if (ngx_delete_file(c->name) == NGX_FILE_ERROR) { |
11 | err = ngx_errno; |
12 | |
13 | if (err != NGX_ENOENT) { |
14 | ngx_log_error(NGX_LOG_CRIT, c->log, err, |
15 | ngx_delete_file_n " \"%s\" failed", c->name); |
16 | } |
17 | } |
18 | |
19 | if (ngx_close_file(c->fd) == NGX_FILE_ERROR) { |
20 | ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, |
21 | ngx_close_file_n " \"%s\" failed", c->name); |
22 | } |
23 | } |
操作系统上内存操作封装
1 | void * ngx_alloc(size_t size, ngx_log_t *log) |
2 | { |
3 | void *p; |
4 | |
5 | p = malloc(size); |
6 | if (p == NULL) { |
7 | ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, |
8 | "malloc(%uz) failed", size); |
9 | } |
10 | |
11 | ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); |
12 | |
13 | return p; |
14 | } |
1 | void * ngx_calloc(size_t size, ngx_log_t *log) |
2 | { |
3 | void *p; |
4 | |
5 | p = ngx_alloc(size, log); |
6 | |
7 | if (p) { |
8 | ngx_memzero(p, size); |
9 | } |
10 | |
11 | return p; |
12 | } |
1 |
|
内存分配流程示例
- 将申请的内存大小
size
与ngx_pool_t
的max
成员比较,以决定申请的是小块内存还是大块内存。如果size<=max
,则继续执行第2步开始分配小块内存,否则跳到第10步分配大块内存。 - 取到
ngx_pool_t
的current
指针,它表示应当首先尝试从这个小块内存池里分配,因为current
之前的pool
已经屡次分配失败(大于4次),其剩余空间多半无法满足size
。这当然是一种浪费的预估,但性能不坏。 - 从当前小块内存池的
ngx_pool_data_t
的last
指针入手,先调用ngx_align_ptr
找到last
后最近的对齐地址。1
2
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & \
3
~((uintptr_t) a - 1))
4
5
6
7
ngx_pool_t *p = ...;
8
u_char* m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
- 比较对齐地址与
ngx_pool_data_t
的end
指针间是否可以容纳size
字节。如果end - m >= size
,那么继续执行第5步准备返回地址m
;否则,在检查ngx_pool_data
的next
指针是否为NULL
,如果是空指针,那么跳到第6步准备再申请新的小块内存池,不为空则跳到第3步继续遍历小块内存池构成的链表。 - 先将
ngx_pool_data_t
的last
指针置为下次空闲内存的首地址,例如:p->d.last = m + size;
再返回地址m,分配内存流程结束。 - 分配一个大小与上一个
ngx_pool_t
一致的内存池专用于小块内存的分配。内存池大小获取简单:(size_t)(pool->d.end - (u_char *)pool)
- 将新内存池的空闲地址的首地址对齐,作为返回给申请的内存,在设last到空闲内存的首地址。
- 从
current
指向的小块内存池开始遍历到当前的新内存池,依次将各failed
成员加1,并把current
指向首个failed<=4
的小块内存池,用于下一次的小块内存分配。 - 返回第7步对齐的地址,分配流程结束。
- 调用
ngx_alloc
方法从进场的堆内存中分配size
大小的内存。 - 遍历
ngx_pool_t
的large
链表,看看有没有ngx_pool_large_t
的alloc
成员值为NULL
(这个alloc
指向的大块内存执行过ngx_pfree
方法)。如果找到了这个ngx_pool_large_t
,继续执行第12步;否则,跳到第13步。为了防止large
链表过大,遍历次数做了限制,例如最多4次还未找到alloc==NULL
的元素,也会跳出这个遍历循环进行第13步。 - 把
ngx_pool_large_t
的alloc
成员设置为第10步分配的内存地址,返回地址,分配流程结束。 - 从内存池中分配出
ngx_pool_large_t
结构体,alloc
成员置为第10步分配的内存地址,将ngx_pool_large_t
添加到ngx_pool_t
的large
链表首部,返回地址,分配流程结束。