访问这里,获取更多原创内容。数组
说明:本系列的文章基于Nginx-1.5.0版本代码。数据结构
本篇开始将涉及到Nginx slab内存管理中与内存释放相关的内容,紧跟上一篇的步伐,趁热打铁,就从“基于块的内存释放”开始吧。ide
开门见源码:函数
void ngx_slab_free(ngx_slab_pool_t *pool, void *p) { ngx_shmtx_lock(&pool->mutex); ngx_slab_free_locked(pool, p); ngx_shmtx_unlock(&pool->mutex); } void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p) { size_t size; uintptr_t slab, m, *bitmap; ngx_uint_t n, type, slot, shift, map; ngx_slab_page_t *slots, *page; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p); /*判断待释放的地址是否在合法的内存空间中*/ if ((u_char *) p < pool->start || (u_char *) p > pool->end) { ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool"); goto fail; } /*因为页内存管理单元与真正的内存页是一一对应的,因此能够经过地址偏移量推算出数组下标,从而找到对应的页内存管理单元*/ n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; page = &pool->pages[n]; /*根据前几篇的内容能够知道slab字段在不一样状况下存储的内容有所不一样*/ slab = page->slab; type = page->prev & NGX_SLAB_PAGE_MASK; /*针对不一样的内存类型进行不一样的操做*/ switch (type) { /*对应chunk_size < exact_size的状况*/ case NGX_SLAB_SMALL: /*在这种状况下,shift值实际多是三、四、五、6,用低4bits就足够存放了*/ shift = slab & NGX_SLAB_SHIFT_MASK; /*计算块大小*/ size = 1 << shift; /*内存块的起始地址p应该与块大小对齐,这一点在内存分配的时候就已经保证了*/ if ((uintptr_t) p & (size - 1)) { goto wrong_chunk; } /*计算地址p对应的内存块在页中的偏移量(是页中的第几个块,n从0开始)*/ n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift; /*(n & (sizeof(uintptr_t) * 8 - 1)): 计算除了若干个uintptr_t类型的值外,还须要多少个bit才能彻底表示n个bit位*/ /*m表示在一个uintptr_t类型中的偏移量*/ m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1)); /*计算要表示n个块所须要的uintptr_t值的个数*/ n /= (sizeof(uintptr_t) * 8); bitmap = (uintptr_t *) ((uintptr_t) p & ~(ngx_pagesize - 1)); /*若是bitmap中对应的位没有置位,表示该块内存已经释放过了,不能重复释放,因而直接跳到chunk_already_free,不然就进行内存释放的相关操做*/ if (bitmap[n] & m) { /*page->next==NULL: 代表对应页中的内存块在此前已经所有分配出去了,那么对其中的一块内存进行释放时还须要将对应的页内存管理单元从新挂接到slot分级管理数组的首部,以便在下次收到内存申请时可以继续从该页分配*/ if (page->next == NULL) { slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); slot = shift - pool->min_shift; page->next = slots[slot].next; slots[slot].next = page; page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL; } /*将对应的置位标记清除*/ bitmap[n] &= ~m; /*计算须要用做bitmap的块数*/ n = (1 << (ngx_pagesize_shift - shift)) / 8 / (1 << shift); if (n == 0) { n = 1; } /*若是bitmap[0]中除了用做bitmap自己的块之外还有未释放的内存块,则直接返回,不然须要继续判断后续内存块是否都已经彻底释放*/ if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) { goto done; } /*计算bitmap数组中的元素个数*/ map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); /*若是还有未释放的内存块,则不作更多操做*/ for (n = 1; n < map; n++) { if (bitmap[n]) { goto done; } } /*不然,将对应的整个内存页释放掉*/ ngx_slab_free_pages(pool, page, 1); goto done; } goto chunk_already_free; /*对应chunk_size == exact_size的状况*/ case NGX_SLAB_EXACT: m = (uintptr_t) 1 << (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift); size = ngx_slab_exact_size; /*一样地,内存块的起始地址须要与块大小对齐*/ if ((uintptr_t) p & (size - 1)) { goto wrong_chunk; } /*这种状况下,slab字段存放的就是bitmap*/ if (slab & m) { /*一样,若是在此以前该页中的内存已经所有分配完毕了,那么须要将该页对应的页内存管理单元从新挂接到slot分级数组下,这样在下一次内存申请时就能够从该页中分配了*/ if (slab == NGX_SLAB_BUSY) { slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); slot = ngx_slab_exact_shift - pool->min_shift; page->next = slots[slot].next; slots[slot].next = page; page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT; } /*将对应的内存块标记为空闲*/ page->slab &= ~m; /*若是该页中还有未释放的内存块,则不作其它操做*/ /*注意,若是前面slab==NGX_SLAB_BUSY成立的话,那么这个判断也必定成立*/ if (page->slab) { goto done; } /*不然释放整页*/ ngx_slab_free_pages(pool, page, 1); goto done; } goto chunk_already_free; /*对应chunk_size > exact_size的状况*/ case NGX_SLAB_BIG: shift = slab & NGX_SLAB_SHIFT_MASK; size = 1 << shift; if ((uintptr_t) p & (size - 1)) { goto wrong_chunk; } /*计算与内存块对应的bit位*/ m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift) + NGX_SLAB_MAP_SHIFT); if (slab & m) { if (page->next == NULL) { slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); slot = shift - pool->min_shift; page->next = slots[slot].next; slots[slot].next = page; page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; page->next->prev = (uintptr_t) page | NGX_SLAB_BIG; } page->slab &= ~m; if (page->slab & NGX_SLAB_MAP_MASK) { goto done; } ngx_slab_free_pages(pool, page, 1); goto done; } goto chunk_already_free; /*若是地址p对应的内存是按页分配的,就进入下面的流程*/ case NGX_SLAB_PAGE: /*这时地址p应该与页大小对齐*/ if ((uintptr_t) p & (ngx_pagesize - 1)) { goto wrong_chunk; } /*NGX_SLAB_PAGE_FREE定义为0,表示该页是空闲的*/ if (slab == NGX_SLAB_PAGE_FREE) { ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): page is already free"); goto fail; } /*NGX_SLAB_PAGE_BUSY表示这不是一段连续内存空间中的第一页*/ if (slab == NGX_SLAB_PAGE_BUSY) { ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): pointer to wrong page"); goto fail; } /*根据内存地址推算页管理单元的下标*/ n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; /*这段连续内存空间中的页数目*/ size = slab & ~NGX_SLAB_PAGE_START; /*将具体实现交给“基于页的内存释放”函数来完成*/ ngx_slab_free_pages(pool, &pool->pages[n], size); ngx_slab_junk(p, size << ngx_pagesize_shift); return; } /* not reached */ return; done: ngx_slab_junk(p, size); return; wrong_chunk: ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): pointer to wrong chunk"); goto fail; /*内存已经释放过了,则什么也不作*/ chunk_already_free: ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): chunk is already free"); fail: return; }
下面咱们基于以下的一种场景来讨论“基于块的内存释放”:布局
------ 假设程序中会大量申请chunk_size = 16bytes的内存块,在将一整页的内存块都分配出去后仍然不能知足需求,因此须要继续重新的内存页中进行划分,直到后来的某一个时刻释放了一块内存,那么这种场景对应的内存布局以下:ui
上面这幅图对应了在将整个的m1内存页都分配出去后,继续从m0内存页分配内存块时的内存布局。须要注意的是,这时与m1内存页对应的内存管理单元并无挂接在相应的slot分级管理链表下。spa
而后在某一时刻,释放了m1内存页中的某个内存块,这时的内存状况布局以下图:.net
能够看到,当向一个已经彻底分配的内存页中释放一个内存块时,这个页对应的页内存管理单元会被从新挂接到相应的slot分级管理链表首部,为的是在下次内存申请时可以继续从该页中进行分配。debug
从代码中咱们还能够看到,当一个页中的内存块已经彻底释放的时候,会调用“基于页的内存释放”函数来完成整个内存页的释放,这一部份内容将会在下一篇中继续讲解。设计
这一篇的内容就结束了,怎么样,有没有再次感觉到Nginx slab分配器设计的巧妙之处,好比创建在”内存对齐”机制上“自我管理“能力,使得在进行内存释放时只需给出内存(块/页)的起始地址,就能够推算出内存类型、待释放的连续内存页数、页内存管理单元地址和分级内存管理单元地址等内容,而无需再使用额外的数据结构。
P.S. 最后再抛出一个问题,Nginx slab在管理小于exact_size的内存块时,实际上是须要将页起始地址处的一部份内存拿来用做bitmap的,并同时将对应的bit置位,以保证在分配时不会将这些块分配出去,但在释放的流程中并无对待释放的地址进行判断,也就是说一个用做bitmap的块地址有可能被“有意”或“无心”的传递进来,从而被不正确地“释放”了,这算不算是“百密不免一疏”呢?