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

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[n] != NGX_SLAB_BUSY) {
212 
213                         for (m = 1, i = 0; m; m <<= 1, i++) {
214                             if ((bitmap[n] & m)) {
                                    // 当前位表示的块已被使用了 
215                                 continue;
216                             }
217 
                                // 找到了还没有被占用的内存块,设置bitmap位占位
218                             bitmap[n] |= 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[n] == NGX_SLAB_BUSY) {
224                                 for (n = n + 1; n < map; n++) {
225                                     if (bitmap[n] != 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[n];
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[n] & 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[n] &= ~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[n]) {
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[n], 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 章:内存池

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nginx是一款高性能的Web服务器软件,它支持SSL/TLS协议,可以为Web应用程序提供安全的访问。本文将介绍如何在Nginx上开启SSL模块,并配置SSL证书,使得Web应用程序支持HTTPS访问。 1. 安装SSL模块 在编译Nginx的时候,需要开启SSL模块。可以在编译选项中加入--with-http_ssl_module参数来开启SSL模块。如果使用的是预编译的二进制包,可以通过查看nginx.conf文件来确定是否开启了SSL模块。如果存在以下配置项,则说明SSL模块已经开启。 ``` listen 443 ssl; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; ``` 2. 配置SSL证书 SSL证书用于加密网站和客户端之间的通信,防止敏感信息在传输过程中被窃取。可以购买SSL证书,也可以使用免费的证书,例如Let's Encrypt。 在配置SSL证书之前,需要先生成证书和私钥。可以使用如下命令生成自签名证书和私钥。 ``` openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt ``` 生成的server.key和server.crt文件分别是私钥和证书。接下来,需要将证书和私钥拷贝到Nginx配置文件所在的目录。 ``` cp server.crt /etc/nginx/ cp server.key /etc/nginx/ ``` 然后,在Nginx配置文件中添加以下配置项,指定证书和私钥的路径。 ``` ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; ``` 3. 配置SSL协议和加密算法 在Nginx配置文件中,可以配置SSL协议和加密算法。可以使用如下配置项指定SSL协议。 ``` ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ``` 可以使用如下配置项指定加密算法。 ``` ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ``` 4. 配置HTTPS监听端口 在Nginx配置文件中,可以使用如下配置项开启HTTPS监听端口。 ``` server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; location / { # 配置Web应用程序的根目录 root /var/www/html; index index.html; } } ``` 在以上配置中,listen 443 ssl指定了HTTPS监听端口,并且ssl_certificate和ssl_certificate_key指定了SSL证书和私钥的路径。location /用于配置Web应用程序的根目录。 5. 重启Nginx服务 完成以上配置后,需要重启Nginx服务,使得配置生效。 ``` service nginx restart ``` 通过以上步骤,就可以在Nginx上开启SSL模块,并配置SSL证书,使得Web应用程序支持HTTPS访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值