您的位置:首页 > 运维架构 > Nginx

Nginx源码分析(2)之——共享内存管理之slab机制

2017-01-10 14:32 861 查看
Refer:

《深入剖析Nginx》 Chapter 3.5 共享内存

《深入理解Nginx–模块开发与架构解析》 Chapter 16 slab共享内存

Nginx源码版本:

nginx-1.10.1

下面直接分析源代码,在代码里进行注释:

--------------------------------
nginx-1.10.1/src/core/ngx_slab.h
--------------------------------
18 struct ngx_slab_page_s {
19     uintptr_t         slab;
20     ngx_slab_page_t  *next;
21     uintptr_t         prev;
22 };
23
24
25 typedef struct {
26     ngx_shmtx_sh_t    lock;
27
28     size_t            min_size;
29     size_t            min_shift;
30
31     ngx_slab_page_t  *pages;
32     ngx_slab_page_t  *last;
33     ngx_slab_page_t   free;
34
35     u_char           *start;
36     u_char           *end;
37
38     ngx_shmtx_t       mutex;
39
40     u_char           *log_ctx;
41     u_char            zero;
42
43     unsigned          log_nomem:1;
44
45     void             *data;
46     void             *addr;
47 } ngx_slab_pool_t;


--------------------------------
nginx-1.10.1/src/core/ngx_slab.c
--------------------------------
72 void
73 ngx_slab_init(ngx_slab_pool_t *pool)
74 {
75     u_char           *p;
76     size_t            size;
77     ngx_int_t         m;
78     ngx_uint_t        i, n, pages;
79     ngx_slab_page_t  *slots;
80
81     /* STUB */
82     if (ngx_slab_max_size == 0) {
83         ngx_slab_max_size = ngx_pagesize / 2;
84         ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
85         for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
86             /* void */
87         }
88     }
89     /**/
90
91     pool->min_size = 1 << pool->min_shift;
92
93     p = (u_char *) pool + sizeof(ngx_slab_pool_t);
94     size = pool->end - p;
95
96     ngx_slab_junk(p, size);
97
98     slots = (ngx_slab_page_t *) p;
99     n = ngx_pagesize_shift - pool->min_shift;
100
101     for (i = 0; i < n; i++) {
102         slots[i].slab = 0;
103         slots[i].next = &slots[i];
104         slots[i].prev = 0;
105     }
106
107     p += n * sizeof(ngx_slab_page_t);
108
109     pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));
110
111     ngx_memzero(p, pages * sizeof(ngx_slab_page_t));
112
113     pool->pages = (ngx_slab_page_t *) p;
114
115     pool->free.prev = 0;
116     pool->free.next = (ngx_slab_page_t *) p;
117
118     pool->pages->slab = pages;
119     pool->pages->next = &pool->free;
120     pool->pages->prev = (uintptr_t) &pool->free;
121
122     pool->start = (u_char *)
123                   ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t),
124                                  ngx_pagesize);
125
126     m = pages - (pool->end - pool->start) / ngx_pagesize;
127     if (m > 0) {
128         pages -= m;
129         pool->pages->slab = pages;
130     }
131
132     pool->last = pool->pages + pages;
133
134     pool->log_nomem = 1;
135     pool->log_ctx = &pool->zero;
136     pool->zero = '\0';
137 }

155 void *
156 ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)
157 {
158     size_t            s;
159     uintptr_t         p, n, m, mask, *bitmap;
160     ngx_uint_t        i, slot, shift, map;
161     ngx_slab_page_t  *page, *prev, *slots;
162

/*
* slab 中把不等长的内存大小分为4个大类:
* 1、小块内存(NGX_SLAB_SMALL):                       内存大小 <  ngx_slab_exact_size
* 2、中等内存(NGX_SLAB_EXACT):                       内存大小 == ngx_slab_exact_size
* 3、大块内存(NGX_SLAB_BIG  ): ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size
* 4、超大内存(NGX_SLAB_PAGE ): ngx_slab_max_size   < 内存大小
*
* ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
* ngx_slab_exact_size 表示 uintptr_t  slab; 变量当作bitmap使用来表示一页内存中内存块的使用状况时,
* slab所有的位(8 * sizeof(uintptr_t))正好不多不少,可以对应到一页内存里所有的内存块时,该页内存该分配
* 成多大的等长内存块
* 一般情况下,ngx_pagesize = getpagesize(); = 4096 byte, 所以,ngx_slab_exact_size = 64 byte
*/

// 4、超大内存,超出slab最大可分配大小,即大于2048,则需要计算出需要的page数
163     if (size > ngx_slab_max_size) {
164
165         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
166                        "slab alloc: %uz", size);
167
/*
* (size >> ngx_pagesize_shift) + ((size % ngx_pagesize) ? 1 : 0)
* 表示要分配多少内存页才能满足超大内存当需求
* 从空闲页中分配出连续的几个可用页面
* 返回的是连续可用页面首页对应的管理结构:ngx_slab_page_t结构,并非真实可用内存的实际对应首地址
*/
168         page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
169                                           + ((size % ngx_pagesize) ? 1 : 0));
170         if (page) {
/*
* 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量
* 再加上真实可用内存的pool->start即本次分配的真实可用内存的实际对应首地址
* 至此,超大内存分配完成,goto done,返回
*/
171             p = (page - pool->pages) << ngx_pagesize_shift;
172             p += (uintptr_t) pool->start;
173
174         } else {
175             p = 0;
176         }
177
178         goto done;
179     }
180
// 如果小于等于2048,则启用slab分配算法进行分配

// 计算出此size的移位数以及此size对应的slot以及移位数
181     if (size > pool->min_size) {
182         shift = 1;
/*
* 计算移位数, 并由移位数得到slot
* 例如:size = 10(pool->min_size = 8), 最后 shift = 4,则 slot = 4 - 3 = 1
* 0 < 内存大小 <= 8  占据 slot[0]
* 8 < 内存大小 <= 16 占据 slot[1]
* shift = 4, slot = 1, 符合预期
*/
183         for (s = size - 1; s >>= 1; shift++) { /* void */ }
184         slot = shift - pool->min_shift;
185
186     } else {
/*
* 小于最小可分配大小的都放到slot[0]里面, 即小于最小可分配大小的内存需求都直接分配最小可分配内存(这里为8byte)
* shift = 3, slot = 0, 符合预期
*/
187         size = pool->min_size;
188         shift = pool->min_shift;
189         slot = 0;
190     }
191
192     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
193                    "slab alloc: %uz slot: %ui", size, slot);
194
/*
* 跳到当前合适页所在的slot数组元素的首个元素
*/
195     slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t));
196     page = slots[slot].next;
197
198     if (page->next != page) {
199
/*
* 1、小块内存
*
* 当从一个页中分配大小小于ngx_slab_exact_shift(ngx_slab_exact_shift = 64)的内存块时
* 无法用uintptr_t  slab;来标识一页内所有内存块的使用情况,因此,
* 这里不用 page->slab 来标识该页内所有内存块的使用情况,而是
* 使用页数据空间的开始几个uintptr_t空间来表示了
* ngx_slab_exact_size = 64 byte, ngx_slab_exact_shift = 6
*/
200         if (shift < ngx_slab_exact_shift) {
201
202             do {
/*
* 同上:
* 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量
* 再加上真实可用内存的pool->start即本次分配的真实可用内存的实际对应首地址
* 并得到“页数据空间的开始几个int空间”的首地址
*/
203                 p = (page - pool->pages) << ngx_pagesize_shift;
204                 bitmap = (uintptr_t *) (pool->start + p);
205
/*
* 计算出到底需要多少个(map个)int来作为bitmap来标识这些内存块空间的使用情况
* ngx_pagesize = getpagesize(); = 4096 byte, ngx_pagesize_shift = 12
* 如:shift = 3,则:一页(4096byte)可分为512个 8byte内存块,需要 map = 16 个int(16 * 4 * 8 bit)来作为bitmap
*    shift = 4, 则:一页(4096byte)可分为256个16byte内存块,需要 map =  8 个int( 8 * 4 * 8 bit)来作为bitmap
*    shift = 5, 则:一页(4096byte)可分为128个32byte内存块,需要 map =  4 个int( 4 * 4 * 8 bit)来作为bitmap
*/
206                 map = (1 << (ngx_pagesize_shift - shift))
207                           / (sizeof(uintptr_t) * 8);
208
209                 for (n = 0; n < map; n++) {
210
/*
* #define NGX_SLAB_BUSY        0xffffffffffffffff
* 表示该bitmap对应的页内内存块都已经被使用
*/
211                     if (bitmap
!= NGX_SLAB_BUSY) {
212
213                         for (m = 1, i = 0; m; m <<= 1, i++) {
214                             if ((bitmap
& m)) {
// 当前位表示的块已被使用了
215                                 continue;
216                             }
217
// 找到了还没有被占用的内存块,设置bitmap位占位
218                             bitmap
|= m;
219
/*
* 每个内存块大小为 1 << shift,
* 现在找到了第n个bitmap的第i位所标识的内存块可用
* 因此,计算得到该块内存的偏移 i
*/
220                             i = ((n * sizeof(uintptr_t) * 8) << shift)
221                                 + (i << shift);
222
/*
* 当当前bitmap可以利用的bit被标识后,并且当前bitmap所对应的内存块都已经被分配完了,则
* 遍历剩下的bitmap位,如果剩下的bitmap都已经被标识,则
* 表示该bitmap数组对应的内存页全部都使用完了,则将当前的page从slot脱离下来(全满页不在任何链表中)
*/
223                             if (bitmap
== NGX_SLAB_BUSY) {
224                                 for (n = n + 1; n < map; n++) {
225                                     if (bitmap
!= NGX_SLAB_BUSY) {
/*
* 该page并非全满页,返回真实可用内存的实际对应首地址
* 其中,bitmap:真实可用内存page的首地址
*      i     : 可分配内存块在该page中的偏移
*/
226                                         p = (uintptr_t) bitmap + i;
227
228                                         goto done;
229                                     }
230                                 }
231
// 分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来
232                                 prev = (ngx_slab_page_t *)
233                                             (page->prev & ~NGX_SLAB_PAGE_MASK);
234                                 prev->next = page->next;
235                                 page->next->prev = page->prev;
236
237                                 page->next = NULL;
238                                 page->prev = NGX_SLAB_SMALL;
239                             }
240
// 同上
241                             p = (uintptr_t) bitmap + i;
242
243                             goto done;
244                         }
245                     }
246                 }
247
248                 page = page->next;
249
250             } while (page); // 这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止
251
252         } else if (shift == ngx_slab_exact_shift) {
253             /*
* 2、中等内存 的情况
*
* 直接用 page->slab 来标识该页内所有内存块的使用情况(刚刚好,bit不多不少)
*/
254             do {
/*
* 同上:
* #define NGX_SLAB_BUSY        0xffffffffffffffff
* 表示该page内所有的内存块都已经被使用
*/
255                 if (page->slab != NGX_SLAB_BUSY) {
256
257                     for (m = 1, i = 0; m; m <<= 1, i++) {
258                         if ((page->slab & m)) {
259                             continue;
260                         }
261
// 同上,找到了还没有被占用的内存块,设置bitmap位占位
262                         page->slab |= m;
263
// 同上,分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来
264                         if (page->slab == NGX_SLAB_BUSY) {
265                             prev = (ngx_slab_page_t *)
266                                             (page->prev & ~NGX_SLAB_PAGE_MASK);
267                             prev->next = page->next;
268                             page->next->prev = page->prev;
269
270                             page->next = NULL;
271                             page->prev = NGX_SLAB_EXACT;
272                         }
273
/*
* 返回真实可用内存的实际对应首地址
* 其中,(page - pool->pages) << ngx_pagesize_shift:该page对应真实可用内存地址相对于pool->start的偏移
*      i << shift                                : 可分配内存块在该page中的偏移
*/
274                         p = (page - pool->pages) << ngx_pagesize_shift;
275                         p += i << shift;
276                         p += (uintptr_t) pool->start;
277
278                         goto done;
279                     }
280                 }
281
282                 page = page->next;
283
284             } while (page); // 同上,这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止
285
286         } else { /* shift > ngx_slab_exact_shift */
287
/*
*                64位系统上
*                64 bytes                        2048 bytes
* 3、大块内存(ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size) 的情况
*
* 当需要分配的空间大于ngx_slab_exact_size = 64 byte时,我们可以用一个int的位来表示这些空间
* 所以我们依然采用跟等于ngx_slab_exact_size时类似的情况,用 page->slab 来标识该page内所有内存块的使用情况
* 此时的page->slab同时存储bitmap及表示内存块大小的shift, 高位为bitmap.
* 这里会有内存块大小依次为:128 bytes、256 bytes、512 bytes、1024 bytes、2048 bytes 等的情况
* 对应有     shift依次为: 7       、 8       、 9       、 10       、 11        等
* 那么采用page->slab的高16位来表示这些空间的占用情况,而最低位,则利用起来表示此页的分配大小,即保存移位数
* 例如:
*     比如我们分配256,当分配第一个空间时,此时的page->slab位图情况是:0x00010008
*     那分配下一空间就是0x00030008了,当为0xffff0008时,就分配完了
*
* #define NGX_SLAB_SHIFT_MASK  0x000000000000000f
* page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数
* 这里用最低的一位十六进制表示就足够了,因为shift最大为11(表示内存块大小为2048 bytes)
* ngx_pagesize_shift减掉后,就是在一页中标记这些块所需要的移位数,也就是块数对应的移位数
* 例如:
* 当页内所能分配的内存块大小为256bytes时,此时,page->slab & NGX_SLAB_SHIFT_MASK = 8
* 因此,n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK) = 12 - 8 = 4
* 即4096 bytes 可以分配16个 256 bytes,因此 n = 1 << n = 16
*/
288             n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK);
// 得到一个页面所能放下的块数
289             n = 1 << n;
// 得到表示这些块数都用完的bitmap,用现在是低32位的
290             n = ((uintptr_t) 1 << n) - 1;
// 将低32位转换成高32位,因为我们是用高32位来表示空间地址的占用情况的,#define NGX_SLAB_MAP_SHIFT   32
291             mask = n << NGX_SLAB_MAP_SHIFT;
292
293             do {
// 表示非全满页
294                 if ((page->slab & NGX_SLAB_MAP_MASK) != mask) {
295
296                     for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
297                          m & mask;
298                          m <<= 1, i++)
299                     {
300                         if ((page->slab & m)) {
301                             continue;
302                         }
303
// 同上,找到了还没有被占用的内存块,设置bitmap位占位
304                         page->slab |= m;
305
// 同上,分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来
306                         if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {
307                             prev = (ngx_slab_page_t *)
308                                             (page->prev & ~NGX_SLAB_PAGE_MASK);
309                             prev->next = page->next;
310                             page->next->prev = page->prev;
311
312                             page->next = NULL;
313                             page->prev = NGX_SLAB_BIG;
314                         }
315
/*
* 返回真实可用内存的实际对应首地址
* 同上:
* 其中,(page - pool->pages) << ngx_pagesize_shift:该page对应真实可用内存地址相对于pool->start的偏移
*      i << shift                                : 可分配内存块在该page中的偏移
*/
316                         p = (page - pool->pages) << ngx_pagesize_shift;
317                         p += i << shift;
318                         p += (uintptr_t) pool->start;
319
320                         goto done;
321                     }
322                 }
323
324                 page = page->next;
325
326             } while (page); // 同上,这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止
327         }
328     }
329
/*
* 在 小块内存、中等内存、大块内存 等三种情况下(不包括超大页面的情况),
* 如果当前slab对应的page中没有空间可分配了,则重新从空闲page中分配一个页
*/
330     page = ngx_slab_alloc_pages(pool, 1);
331
332     if (page) {
333         if (shift < ngx_slab_exact_shift) {
334             p = (page - pool->pages) << ngx_pagesize_shift;
335             bitmap = (uintptr_t *) (pool->start + p);
336
/*
* 这里shift代表要分配多大内存块的移位数,因此
* s即需要分配内存块的大小
* n表示page会被分成多少个大小为s的内存块
*/
337             s = 1 << shift;
338             n = (1 << (ngx_pagesize_shift - shift)) / 8 / s;
339
340             if (n == 0) {
341                 n = 1;
342             }
343
344             bitmap[0] = (2 << n) - 1;
345
// 计算出到底需要多少个(map个)int来作为bitmap来标识这些内存块空间的使用情况
346             map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);
347
// 将剩下的bitmap数组全部初识化为0,除了bitmap[0],前面已经进行过置位了
348             for (i = 1; i < map; i++) {
349                 bitmap[i] = 0;
350             }
351
// 在 1、小块内存 中,page->slab存放等长内存块的大小(用位偏移的方式存储)
352             page->slab = shift;
353             page->next = &slots[slot];
354             page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
355
356             slots[slot].next = page;
357
358             p = ((page - pool->pages) << ngx_pagesize_shift) + s * n;
359             p += (uintptr_t) pool->start;
360
361             goto done;
362
363         } else if (shift == ngx_slab_exact_shift) {
364
365             page->slab = 1;
366             page->next = &slots[slot];
367             page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
368
369             slots[slot].next = page;
370
371             p = (page - pool->pages) << ngx_pagesize_shift;
372             p += (uintptr_t) pool->start;
373
374             goto done;
375
376         } else { /* shift > ngx_slab_exact_shift */
377
378             page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;
379             page->next = &slots[slot];
380             page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
381
382             slots[slot].next = page;
383
384             p = (page - pool->pages) << ngx_pagesize_shift;
385             p += (uintptr_t) pool->start;
386
387             goto done;
388         }
389     }
390
391     p = 0;
392
393 done:
394
395     ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
396                    "slab alloc: %p", (void *) p);
397
398     return (void *) p;
399 }

442 void
443 ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p)
444 {
445     size_t            size;
446     uintptr_t         slab, m, *bitmap;
447     ngx_uint_t        n, type, slot, shift, map;
448     ngx_slab_page_t  *slots, *page;
449
450     ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p);
451
// 剔除异常情况
452     if ((u_char *) p < pool->start || (u_char *) p > pool->end) {
453         ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool");
454         goto fail;
455     }
456
// 计算page偏移,并找到相应的ngx_slab_page_t结构(slab_page管理结构)
457     n = ((u_char *) p - pool->start) >> ngx_pagesize_shift;
458     page = &pool->pages
;
459     slab = page->slab;
460     type = page->prev & NGX_SLAB_PAGE_MASK;
461
462     switch (type) {
463
464     case NGX_SLAB_SMALL:
465
/* 1、小块内存
*
* 无法用uintptr_t  slab;来标识一页内所有内存块的使用情况,因此,
* 这里不用 page->slab 来标识该页内所有内存块的使用情况,而是
* 使用页数据空间的开始几个uintptr_t空间来表示了
*
* 在 1、小块内存 中,page->slab存放等长内存块的大小(用位偏移的方式存储)
* 因此,size即小块内存块的大小
*/
466         shift = slab & NGX_SLAB_SHIFT_MASK;
467         size = 1 << shift;
468
// 由于已经进行过内存对齐, 所以p的地址一定是slot(小块内存块)大小的整数倍,否则异常
469         if ((uintptr_t) p & (size - 1)) {
470             goto wrong_chunk;
471         }
472
/*
* 这里很巧妙:
* 由于前面对页进行了内存对齐的处理,因此下面的式子可直接
*
* 求出p对应的slot块的位置,即p对应的小块内存位于page中的第几个块
*/
473         n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift;
// 求出在uintptr_t中,p对应的偏移,即求出在uintptr_t中的第几位
474         m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1));
/*
* 由于小块内存的bitmap是使用页数据空间的开始几个uintptr_t空间来表示的
* 所以求出该小块内存对应的uintptr_t的偏移,即求出第几个uintptr_t
*
* 至此,即(页数据空间的开始几个uintptr_t空间)第n个uintptr_t的第m位用来标识该小块内存的使用情况
*/
475         n /= (sizeof(uintptr_t) * 8);
/*
* 求出p对应的page页的位置,即真实内存的地址,主要因为是已经进行过了内存页对齐,所以这里可以这样直接计算出page首地址
* 这里因为小块内存的bitmap是使用页数据空间的开始几个uintptr_t空间来表示的
* 因此,page页的首地址即bitmap的首地址
*/
476         bitmap = (uintptr_t *)
477                              ((uintptr_t) p & ~((uintptr_t) ngx_pagesize - 1));
478
// 如果(bitmap)第n个uintptr_t的第m位确实为1
479         if (bitmap
& m) {
480
// 如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中
481             if (page->next == NULL) {
482                 slots = (ngx_slab_page_t *)
483                                    ((u_char *) pool + sizeof(ngx_slab_pool_t));
484                 slot = shift - pool->min_shift;
485
486                 page->next = slots[slot].next;
487                 slots[slot].next = page;
488
489                 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
490                 page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL;
491             }
492
// 设置bitmap上对应位置为可用,即0
493             bitmap
&= ~m;
494
/*
* 下面的操作主要是查看这个页面是否都没用(空闲页),  如果是空闲页,则将页面链入free中
*
* shift即page->slab存放等长内存块的大小(用位偏移的方式存储)
* 计算位图存储了多少块
* 例如:
* 假设小块内存大小为 32 bytes < 64 bytes, 因此shift = 5
* 4096 bytes 可以分为  128  个 32 bytes,因此只需要一个 32 bytes的小块内存作为 bitmap 即可
* 因此 n = (1 << (12 - 5)) / 8 / (1 << 5) = 0,修正n = 1,正如上所期望
*/
495             n = (1 << (ngx_pagesize_shift - shift)) / 8 / (1 << shift);
496
497             if (n == 0) {
498                 n = 1;
499             }
500
501             if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) {
502                 goto done;
503             }
504
// 计算位图使用了多少个uintptr_t
505             map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);
506
// 查看其他uintptr_t是否都没使用
507             for (n = 1; n < map; n++) {
508                 if (bitmap
) {
509                     goto done;
510                 }
511             }
512
// 如果释放该小块内存后,page变为空闲页,则进行进一步的回收
513             ngx_slab_free_pages(pool, page, 1);
514
515             goto done;
516         }
517
518         goto chunk_already_free;
519
520     case NGX_SLAB_EXACT:
521
/*
* p所对应的slot块在slab(slot位图)中的位置.
* (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift) 对应了第几个位
* 因此,m 直接对应了位图
*/
522         m = (uintptr_t) 1 <<
523                 (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift);
524         size = ngx_slab_exact_size;
525
// 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍,否则异常
526         if ((uintptr_t) p & (size - 1)) {
527             goto wrong_chunk;
528         }
529
// 该 NGX_SLAB_EXACT 内存块对应的bitmap位为1
530         if (slab & m) {
// 同上,如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中
531             if (slab == NGX_SLAB_BUSY) {
532                 slots = (ngx_slab_page_t *)
533                                    ((u_char *) pool + sizeof(ngx_slab_pool_t));
534                 slot = ngx_slab_exact_shift - pool->min_shift;
535
536                 page->next = slots[slot].next;
537                 slots[slot].next = page;
538
539                 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
540                 page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT;
541             }
542
543             page->slab &= ~m;
544
// 释放完内存块后,当前页状态为非满页,则跳转,避免后面的空闲页释放
545             if (page->slab) {
546                 goto done;
547             }
548
549             ngx_slab_free_pages(pool, page, 1);
550
551             goto done;
552         }
553
554         goto chunk_already_free;
555
556     case NGX_SLAB_BIG:
557
/*
* slab的高32位是slot块的位图,低32位用于存储slot块大小的偏移
* #define NGX_SLAB_SHIFT_MASK  0x0000000f
*                               64位系统上
*                64 bytes                        2048 bytes
* 3、大块内存(ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size) 的情况
* page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数
* 这里用最低的一位十六进制表示就足够了,因为shift最大为11(表示内存块大小为2048 bytes
* 因此,size为大块内存块的大小
*/
558         shift = slab & NGX_SLAB_SHIFT_MASK;
559         size = 1 << shift;
560
// 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍,否则异常
561         if ((uintptr_t) p & (size - 1)) {
562             goto wrong_chunk;
563         }
564
// 计算出该内存块对应的位图 m
565         m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift)
566                               + NGX_SLAB_MAP_SHIFT);
567
568         if (slab & m) {
569
// 同上,如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中
570             if (page->next == NULL) {
571                 slots = (ngx_slab_page_t *)
572                                    ((u_char *) pool + sizeof(ngx_slab_pool_t));
573                 slot = shift - pool->min_shift;
574
575                 page->next = slots[slot].next;
576                 slots[slot].next = page;
577
578                 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
579                 page->next->prev = (uintptr_t) page | NGX_SLAB_BIG;
580             }
581
582             page->slab &= ~m;
583
// #define NGX_SLAB_MAP_MASK    0xffffffff00000000
// 非空闲页,则跳过释放内存块后的空闲页释放
584             if (page->slab & NGX_SLAB_MAP_MASK) {
585                 goto done;
586             }
587
588             ngx_slab_free_pages(pool, page, 1);
589
590             goto done;
591         }
592
593         goto chunk_already_free;
594
595     case NGX_SLAB_PAGE:
596
// 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍(这里大块内存,所以地址跟内存页对其),否则异常
597         if ((uintptr_t) p & (ngx_pagesize - 1)) {
598             goto wrong_chunk;
599         }
600
601         if (slab == NGX_SLAB_PAGE_FREE) {
602             ngx_slab_error(pool, NGX_LOG_ALERT,
603                            "ngx_slab_free(): page is already free");
604             goto fail;
605         }
606
/*
* 超大内存会使用1页或者多页,这些页在一起使用
* 对于这批页面中的第1页,slab的前3位会被设置为NGX_SLAB_PAGE_STATRT, 其余位表示紧随其后相邻的同批页面数
* 紧随其后相邻的同批页面的slab会被设置为NGX_SLAB_PAGE_BUSY
*/
607         if (slab == NGX_SLAB_PAGE_BUSY) {
608             ngx_slab_error(pool, NGX_LOG_ALERT,
609                            "ngx_slab_free(): pointer to wrong page");
610             goto fail;
611         }
612
// 计算首地址在pool->start开始的第几个页,size表示同批页面中总共有几个相邻页,即需要归还的页面数
613         n = ((u_char *) p - pool->start) >> ngx_pagesize_shift;
614         size = slab & ~NGX_SLAB_PAGE_START;
615
// 归还size个页面
616         ngx_slab_free_pages(pool, &pool->pages
, size);
617
618         ngx_slab_junk(p, size << ngx_pagesize_shift);
619
620         return;
621     }
622
623     /* not reached */
624
625     return;
626
627 done:
628
629     ngx_slab_junk(p, size);
630
631     return;
632
633 wrong_chunk:
634
635     ngx_slab_error(pool, NGX_LOG_ALERT,
636                    "ngx_slab_free(): pointer to wrong chunk");
637
638     goto fail;
639
640 chunk_already_free:
641
642     ngx_slab_error(pool, NGX_LOG_ALERT,
643                    "ngx_slab_free(): chunk is already free");
644
645 fail:
646
647     return;
648 }

/*
* 在slab共享内存的管理结构ngx_slab_pool_t中有一个ngx_slab_page_t  *pages成员用来存储所有页面的描述结构ngx_slab_page_t
* 注意这里只是返回了对应页面的ngx_slab_page_t管理结构,并没有返回实际对应的真实内存地址
*/
651 static ngx_slab_page_t *
652 ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
653 {
654     ngx_slab_page_t  *page, *p;
655
// 遍历free空闲页链表
656     for (page = pool->free.next; page != &pool->free; page = page->next) {
657
/*
* 页管理结构ngx_slab_page_t,当页为空闲页时,其成员slab表示相邻的空闲页数
*
* 这里表明有足够多的连续空闲页可供分配
*/
658         if (page->slab >= pages) {
659
// 如果链表中的这个页描述指明的连续页面数大于要求的pages,只取所需即可
// 将剩余的连续页面数仍然作为一个链表元素放在free池中
660             if (page->slab > pages) {
661                 page[page->slab - 1].prev = (uintptr_t) &page[pages];
662
663                 page[pages].slab = page->slab - pages;
664                 page[pages].next = page->next;
665                 page[pages].prev = page->prev;
666
667                 p = (ngx_slab_page_t *) page->prev;
668                 p->next = &page[pages];
669                 page->next->prev = (uintptr_t) &page[pages];
670
671             } else {
// slab等于pages时,直接将pages页描述移出free链表即可
672                 p = (ngx_slab_page_t *) page->prev;
673                 p->next = page->next;
674                 page->next->prev = page->prev;
675             }
676
/*
* #define NGX_SLAB_PAGE_START  0x8000000000000000
* 这段连续页面的首页描述的slab里,高3位设置为NGX_SLAB_PAGE_START
*/
677             page->slab = pages | NGX_SLAB_PAGE_START;
678             page->next = NULL;
// prev定义页类型:存放size > ngx_slab_max_size的页级别内存块
679             page->prev = NGX_SLAB_PAGE;
680
// 如果只分配里1页,则直接返回
681             if (--pages == 0) {
682                 return page;
683             }
684
// 如果分配了连续多个页面,则将后续的页描述也进行相应的初识化
685             for (p = page + 1; pages; pages--) {
686                 p->slab = NGX_SLAB_PAGE_BUSY;
687                 p->next = NULL;
688                 p->prev = NGX_SLAB_PAGE;
689                 p++;
690             }
691
692             return page;
693         }
694     }
695
696     if (pool->log_nomem) {
697         ngx_slab_error(pool, NGX_LOG_CRIT,
698                        "ngx_slab_alloc() failed: no memory");
699     }
700
701     return NULL;
702 }

/*
* 页面释放函数并不会将相邻的两个可用页面合并,仅仅将归还的页面链入free中,
* 所以当用户请求的页面大于一页的时候要特别注意,尽量不要是使用slab_pool,否则很可能失败
*/
705 static void
706 ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page,
707     ngx_uint_t pages)
708 {
709     ngx_uint_t        type;
710     ngx_slab_page_t  *prev, *join;
711
// 计算第1页后跟的page的数目
712     page->slab = pages--;
713
// 如果是多页的情况,对跟的page的page管理结构slab_page_t进行清空。
714     if (pages) {
715         ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t));
716     }
717
// 如果page后面还跟有节点,则将其连接至page的前一个结点
718     if (page->next) {
719         prev = (ngx_slab_page_t *) (page->prev & ~NGX_SLAB_PAGE_MASK);
720         prev->next = page->next;
721         page->next->prev = page->prev;
722     }
723
724     join = page + page->slab;
725
726     if (join < pool->last) {
727         type = join->prev & NGX_SLAB_PAGE_MASK;
728
729         if (type == NGX_SLAB_PAGE) {
730
731             if (join->next != NULL) {
732                 pages += join->slab;
733                 page->slab += join->slab;
734
735                 prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK);
736                 prev->next = join->next;
737                 join->next->prev = join->prev;
738
739                 join->slab = NGX_SLAB_PAGE_FREE;
740                 join->next = NULL;
741                 join->prev = NGX_SLAB_PAGE;
742             }
743         }
744     }
745
746     if (page > pool->pages) {
747         join = page - 1;
748         type = join->prev & NGX_SLAB_PAGE_MASK;
749
750         if (type == NGX_SLAB_PAGE) {
751
752             if (join->slab == NGX_SLAB_PAGE_FREE) {
753                 join = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK);
754             }
755
756             if (join->next != NULL) {
757                 pages += join->slab;
758                 join->slab += page->slab;
759
760                 prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK);
761                 prev->next = join->next;
762                 join->next->prev = join->prev;
763
764                 page->slab = NGX_SLAB_PAGE_FREE;
765                 page->next = NULL;
766                 page->prev = NGX_SLAB_PAGE;
767
768                 page = join;
769             }
770         }
771     }
772
773     if (pages) {
774         page[pages].prev = (uintptr_t) page;
775     }
776
// 将page重新归于slab_page_t的管理结构之下,放于管理结构的头部。
777     page->prev = (uintptr_t) &pool->free;
778     page->next = pool->free.next;
779
780     page->next->prev = (uintptr_t) page;
781
782     pool->free.next = page;
783 }


参考

Nginx内存管理及数据结构浅析–内存池

nginx 内存池分析

nginx slab内存管理

内存池到底为我们解决了什么问题

C++ 应用程序性能优化,第 6 章:内存池
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内存管理 nginx