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

来源:互联网 发布:关于网络的手抄报图片 编辑:程序博客网 时间:2024/04/30 17:36

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   32291             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 void443 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位确实为1479         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位为1530         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             // 计算出该内存块对应的位图 m565         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 void706 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 章:内存池

0 0