免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 30338 | 回复: 44
打印 上一主题 下一主题

Nginx源代码情景分析(3)——Nginx内存管理 [复制链接]

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-02 22:05 |只看该作者 |倒序浏览
第3章 Nginx内存管理
内存管理是各个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 };
这个组织比较简单就是一个链表。
下图展示了内存的管理组织结构:

图 4 内存管理结构
接下来,我们深入分析内存管理的主要函数。
(1)ngx_create_pool(size_t size, ngx_log_t *log)
size:分配内存的大小
log:用于日志记录
下面是该函数的具体实现
15 ngx_pool_t *
16 ngx_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 *
51 ngx_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
44 ngx_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 }
该片段彻底销毁内存池本身。

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [报告]
发表于 2011-12-02 22:08 |只看该作者
本帖最后由 dreamice 于 2011-12-04 14:33 编辑

(3)void ngx_reset_pool(ngx_pool_t *pool)
重置内存池,将内存池恢复到刚分配时的初始化状态,注意内存池分配的初始状态时,是不包含大块内存的,因此初始状态需要将使用的大块内存释放掉,并把内存池数据结构的各项指针恢复到初始状态值。代码片段如下:
95 void
96 ngx_reset_pool(ngx_pool_t *pool)
97 {
98     ngx_pool_t        *p;
99     ngx_pool_large_t  *l;
100
101     for (l = pool->large; l; l = l->next) {
102         if (l->alloc) {
103             ngx_free(l->alloc);
104         }
105     }
上述片段主要用于清理使用到的大块内存。
106
107     pool->large = NULL;
108
109     for (p = pool; p; p = p->d.next) {
110         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
111     }
112 }
这里虽然重置了内存池,但可以看到并没有释放内存池中被使用的小块内存,而只是将其last指针指向可共分配的内存的初始位置。这样,就省去了内存池的释放和重新分配操作,而达到重置内存池的目的。
以上三个函数主要阐述了内存池管理的几个函数,接下来我们深入到如何从内存池中去申请使用内存。
(4)ngx_palloc 与ngx_pnalloc函数

这两个函数的参数都为(ngx_pool_t *pool, size_t size),且返回类型为void*,唯一的区别是ngx_palloc从pool内存池分配以NGX_ALIGNMENT对齐的内存,而ngx_pnalloc分配适合size大小的内存,不考虑内存对齐。我们在这里只ngx_palloc,对于ngx_pnalloc其实现方式基本类似。
文件:src/core/ngx_palloc.c
115 void *
116 ngx_palloc(ngx_pool_t *pool, size_t size)
117 {
118     u_char      *m;
119     ngx_pool_t  *p;
120
121     if (size <= pool->max) {
122
123         p = pool->current;
124
125         do {
126             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
127
128             if ((size_t) (p->d.end - m) >= size) {
129                 p->d.last = m + size;
130
131                 return m;
132             }
133
134             p = p->d.next;
135
136         } while (p);
137
138         return ngx_palloc_block(pool, size);
139     }
140
141     return ngx_palloc_large(pool, size);
142 }
上述函数实现中,121行执行判断,ngx_palloc只分配size小于pool->max的内存块,如果大于max值,则跳到141行的执行大块内存分配ngx_palloc_large。
先考虑所分配的内存块小于max的情况,在126行,执行对齐操作,即以last开始,计算以NGX_ALIGNMENT对齐的偏移位置指针,然后进入128行,计算end值减去这个偏移指针位置的大小是否满足索要分配的size大小,如果满足,则移动last指针位置,并返回所分配到的内存地址的起始地址;如果不满足,则查找下一个链。如果遍历完整个内存池链表均为找到合适大小的内存块供分配,则执行ngx_palloc_block()来分配。
ngx_palloc_block()函数为该内存池再分配一个block,该block的大小为链表中前面每一个block大小的值。一个内存池是由多个block链接起来的。分配成功后,将该block链入该poll链的最后,同时,为所要分配的size大小的内存进行分配,并返回分配内存的起始地址。
文件:src/core/ngx_palloc.c
175 static void *
176 ngx_palloc_block(ngx_pool_t *pool, size_t size)
177 {
178     u_char      *m;
179     size_t       psize;
180     ngx_pool_t  *p, *new, *current;
181
182     psize = (size_t) (pool->d.end - (u_char *) pool);
这里计算需要分配的block的大小
183
184     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
185     if (m == NULL) {
186         return NULL;
187     }
执行按NGX_POOL_ALIGNMENT对齐方式的内存分配,假设能够分配成功,则继续执行后续代码片段。
188
189     new = (ngx_pool_t *) m;
190
191     new->d.end = m + psize;
192     new->d.next = NULL;
193     new->d.failed = 0;
执行该block相关的初始化。
194
195     m += sizeof(ngx_pool_data_t);
196     m = ngx_align_ptr(m, NGX_ALIGNMENT);
197     new->d.last = m + size;
对请求的内存执行分配任务。
198
199     current = pool->current;
200
201     for (p = current; p->d.next; p = p->d.next) {
202         if (p->d.failed++ > 4) {
203             current = p->d.next;
204         }
205     }
206
207     p->d.next = new;
将分配的block链入内存池
208
209     pool->current = current ? current : new;
如果是第一次为内存池分配block,这current将指向新分配的block。
210
211     return m;
212 }
接下来,如果分配的内存大小大于max值,代码将跳到ngx_palloc_large(pool, size)位置,进入ngx_palloc_large(pool, size)函数的分析:
文件:src/core/ngx_palloc.c
215 static void *
216 ngx_palloc_large(ngx_pool_t *pool, size_t size)
217 {
218     void              *p;
219     ngx_uint_t         n;
220     ngx_pool_large_t  *large;
221
222     p = ngx_alloc(size, pool->log);
223     if (p == NULL) {
224         return NULL;
225     }
226
227     n = 0;
228
229     for (large = pool->large; large; large = large->next) {
230         if (large->alloc == NULL) {
231             large->alloc = p;
232             return p;
233         }
234
235         if (n++ > 3) {
236             break;
237         }
238     }
239
240     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
241     if (large == NULL) {
242         ngx_free(p);
243         return NULL;
244     }
245
246     large->alloc = p;
247     large->next = pool->large;
248     pool->large = large;
249
250     return p;
251 }
这是一个static的函数,说明外部函数不会随便调用,而是提供给内部分配调用的,即nginx在进行内存分配需求时,不会自行去判断是否是大块内存还是小块内存,而是交由内存分配函数去判断,对于用户需求来说是完全透明的。
函数在222行调用ngx_alloc执行内存分配:
16 void *
17 ngx_alloc(size_t size, ngx_log_t *log)
18 {
19     void  *p;
20
21     p = malloc(size);
22     if (p == NULL) {
23         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
24                       "malloc(%uz) failed", size);
25     }
26
27     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
28
29     return p;
30 }
从ngx_alloc的第21行可以看到,实际上就是调用malloc函数分配内存的。再回到ngx_palloc_large函数中。第229—238行,将分配的内存链入pool的large链中,这里指原始pool在之前已经分配过large内存的情况。如果该pool之前并未分配large内存,则就没有ngx_pool_large_t来管理大块内存,因此进入第240—248行,执行ngx_pool_large_t结构体的分配,用于来管理large内存块。至此,ngx_palloc对内存的申请分配结束。



未完待续

论坛徽章:
0
3 [报告]
发表于 2011-12-03 11:57 |只看该作者
支持!

论坛徽章:
0
4 [报告]
发表于 2011-12-03 19:19 |只看该作者
刚接触nginx源代码,对内存池的使用有个疑问想问问楼主:
ngnix对一个Pool的使用如何划定粒度的? 比如我记得一个http连接会create一个pool,然后对于这个http连接内部的所有内存分配都会在这个pool上。楼主能不能站在总体的高度上,划分一下有多少类模块独立使用自己的Pool?

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
5 [报告]
发表于 2011-12-03 20:16 |只看该作者
刚接触nginx源代码,对内存池的使用有个疑问想问问楼主:
ngnix对一个Pool的使用如何划定粒度的? 比如我记 ...
gqbfree 发表于 2011-12-03 19:19



    这块我接下来会尽量分析清楚,因为我也刚深入分析nginx源代码,有些地方需要加深,多谢提醒。希望加入进来一起分析!

论坛徽章:
1
双子座
日期:2013-11-06 17:18:01
6 [报告]
发表于 2011-12-04 00:24 |只看该作者
LZ这样的画的图怎么都这么好看.段大也是. 用的visio吗?

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
7 [报告]
发表于 2011-12-04 14:33 |只看该作者
二楼有更新!

论坛徽章:
0
8 [报告]
发表于 2011-12-05 13:49 |只看该作者
本人一直在nginx上做开发,希望多向楼主学习交流啊!

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
9 [报告]
发表于 2011-12-05 15:13 |只看该作者
本人一直在nginx上做开发,希望多向楼主学习交流啊!
dingyujie 发表于 2011-12-05 13:49


大师啊,以后多多提供以下实战经验和模块开发的实例供大家交流学习吧!

论坛徽章:
1
双子座
日期:2013-11-06 17:18:01
10 [报告]
发表于 2011-12-06 20:50 |只看该作者
回复 9# dreamice


    nginx qq群是CU的还是LZ自己的,会长期有效吗?
.虽然我都没接触过编程,但希望LZ继续下去.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP