内存管理是各个WEB服务器都相继实现了的独立功能,作为一个满足高性能的WEB服务器,面对各种请求和应答处理流程,必然涉及到内存以及连接的分配与管理,如果完全采用标准的malloc/free函数接口实现内存管理,频繁的调用必然引起性能的低效。Nginx也不例外,采用了短小精干的方式,实现了其特有的内存管理方式。通过这部分的分析学习,希望我们也能达到融会贯通的目的,不仅能深入理解Nginx的内存管理机制,在实际应用中也能学到其独特的机制,并加以运用。对于开发Nginx模块来说,就更要熟悉其内存管理的相关接口。
内存相关的操作主要在文件 os/unix/ngx_alloc.{h,c} 和core/ngx_palloc.{h,c} 中实现。先分析内存管理的几个主要数据结构:
14 typedef struct ngx_pool_s ngx_pool_t;
48 typedef struct {
49 u_char *last;
50 u_char *end;
51 ngx_pool_t *next;
52 ngx_uint_t failed;
53 } ngx_pool_data_t;
Last:当前内存分配结束位置,即下一段可分配内存的起始位置;
End:内存池的结束位置;
Next:链接到下一个内存池
Failded:记录内存分配不能满足需求的失败次数;
ngx_pool_data_t;结构用来维护内存池的数据块,供用户分配之用。
54
55
56 struct ngx_pool_s {
57 ngx_pool_data_t d;
58 size_t max;
59 ngx_pool_t *current;
60 ngx_chain_t *chain;
61 ngx_pool_large_t *large;
62 ngx_pool_cleanup_t *cleanup;
63 ngx_log_t *log;
64 };
d:数据块
max:数据块大小,小块内存的最大值
current:指向本内存池
chain:这里可以挂一个链结构
large:指向大块内存分配,nginx中,大块内存分配直接采用标准系统接口malloc
cleanup:析构函数,挂载内存释放时需要清理资源的一些必要操作;
log:内存分配相关的日志记录
再看看大块数据分配的结构体:
42 struct ngx_pool_large_s {
43 ngx_pool_large_t *next;
44 void *alloc;
45 };
这个组织比较简单就是一个链表。
下图展示了内存的管理组织结构:接下来,我们深入分析内存管理的主要函数。
(1)ngx_create_pool(size_t size, ngx_log_t *log)
size:分配内存的大小
log:用于日志记录
下面是该函数的具体实现
15 ngx_pool_t *
16ngx_create_pool(size_t size, ngx_log_t *log)
17 {
18 ngx_pool_t *p;
19
20 p= ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
21 if (p == NULL) {
22 return NULL;
23 }
24
25 p->d.last = (u_char *) p + sizeof(ngx_pool_t);
26 p->d.end = (u_char *) p + size;
27 p->d.next = NULL;
28 p->d.failed = 0;
29
30 size = size - sizeof(ngx_pool_t);
31 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size :NGX_MAX_ALLOC_FROM_POOL;
32
33 p->current = p;
34 p->chain = NULL;
35 p->large = NULL;
36 p->cleanup = NULL;
37 p->log = log;
38
39 return p;
40 }
第20行ngx_memalign()函数执行内存分配,该函数的实现在src/os/unix/ngx_alloc.c文件中(假定NGX_HAVE_POSIX_MEMALIGN被定义):
50 void *
51ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
52 {
53 void *p;
54 int err;
55
56 err = posix_memalign(&p, alignment, size);
该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
57
58 if (err) {
59 ngx_log_error(NGX_LOG_EMERG, log, err,
60 "posix_memalign(%uz, %uz)failed", alignment, size);
61 p = NULL;
62 }
63
64 ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
65 "posix_memalign:%p:%uz @%uz", p, size, alignment);
66
67 return p;
68 }
从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
23 #define NGX_POOL_ALIGNMENT 16
因此,nginx的内存池分配,是以16字节为边界对齐的。
函数的25—40行,是按照图4的所示的结构进行组织进行初始化,具体实现读者可以对照图4来加以理解。
(2)void ngx_destroy_pool(ngx_pool_t *pool)
内存池的销毁函数,pool指向需要销毁的内存池。
43 void
44ngx_destroy_pool(ngx_pool_t *pool)
45 {
46 ngx_pool_t *p, *n;
47 ngx_pool_large_t *l;
48 ngx_pool_cleanup_t *c;
49
50 for (c = pool->cleanup; c; c = c->next) {
51 if (c->handler) {
52 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
53 "run cleanup:%p", c);
54 c->handler(c->data);
55 }
56 }
前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
57
58 for (l = pool->large; l; l = l->next) {
59
60 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free:%p", l->alloc);
61
62 if (l->alloc) {
63 ngx_free(l->alloc);
64 }
65 }
这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,即大内存块就是通过malloc和free操作进行管理的。
66
67#if (NGX_DEBUG)
68
69 /*
70 * we could allocate the pool->log from this pool
71 * so we cannot use this log while free()ing the pool
72 */
73
74 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next){
75 ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
76 "free: %p, unused:%uz", p, p->d.end - p->d.last);
77
78 if (n == NULL) {
79 break;
80 }
81 }
82
只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
83#endif
84
85 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next){
86 ngx_free(p);
87
88 if (n == NULL) {
89 break;
90 }
91 }
92 }
该片段彻底销毁内存池本身。