#內存管理的藝術# 之 Nginx slab的實現 --- 第四篇「基於塊的內存釋放」

訪問這裏,獲取更多原創內容。數組

    說明:本系列的文章基於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的塊地址有可能被「有意」或「無心」的傳遞進來,從而被不正確地「釋放」了,這算不算是「百密不免一疏」呢?

相關文章
相關標籤/搜索