nginx中slab實現


slab的一些結構體:

typedef struct {  
    ngx_atomic_t      lock; // 鎖,因為slab在nginx中一般配合共享內存使用  
  
    size_t            min_size; // 分配空間的最小值  
    size_t            min_shift;    // 該最小值對應的移位數  
  
    ngx_slab_page_t  *pages; // 頁數組  
    ngx_slab_page_t   free; // 空閑的頁  
  
    u_char           *start;    // 分配地址開始地址  
    u_char           *end;    
  
    ngx_shmtx_t       mutex;  
  
    u_char           *log_ctx;  
    u_char            zero;  
  
    void             *data;  
    void             *addr;  
} ngx_slab_pool_t;  
  
// 頁結構體  
struct ngx_slab_page_s {  
    uintptr_t         slab;     // 保存當前頁的一些信息  
    ngx_slab_page_t  *next;// 下一個  
    uintptr_t         prev;// 上一個  
};  

slab的函數調用:

// 初始化slab池  
void ngx_slab_init(ngx_slab_pool_t *pool);  
// 未加鎖的  
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);  
// 在調用前已加鎖,分配指定大小空間  
void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);  
void ngx_slab_free(ngx_slab_pool_t *pool, void *p);  
// 釋放空間  
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);  

關於slab的使用,我們在介紹nginx中共享內存的時候再去介紹吧,我們只需要知道在進程初始化時,ngx_init_cycle函數會調用ngx_init_zone_pool來初始化共享內存,然后在ngx_init_zone_pool函數中會調用ngx_slab_init來初始化slab內存池。隨后,在進程中,我們就可以調用alloc與free來對共享內存進行操作了。

對於64位與32位系統,nginx里面默認的值是不一樣的,我們看到數字可能會更好理解一點,所以我們就以32位來看,用實際的數字來說話!

 

// 頁大小
ngx_pagesize: 4096
// 頁大小對應的移位數
ngx_pagesize_shift: 12
// slab的一次最大分配空間,默認為pagesize/2
ngx_slab_max_size: 2048
// slab精確分配大小,這個是一個分界點,通常是4096/32,為什么會這樣,我們后面會有介紹
ngx_slab_exact_size: 128
// slab精確分配大小對應的移位數
ngx_slab_exact_shift: 7

ngx_slab_exact_size這個時依賴slab的分配算法.它的值是這樣來的.4096/32,2048是slab頁大小,而32是一個int的位數,最后的值是128。
why? 我們在分配時,在一頁中,我們可以將這一頁分成多個塊,而某個塊需要標記是否被分配,而一頁空間正好被分成32個128字節大小的塊,於是我們可以用一個int的32位表示這塊的使用情況,而此時,我們是使用ngx_slab_page_s結構體中的slab成員來表示塊的使用情況的。另外,在分配大於128與小於128時,表示塊的占用情況有所有同
// 初始化  
void  
ngx_slab_init(ngx_slab_pool_t *pool)  
{  
    u_char           *p;  
    size_t            size;  
    ngx_int_t         m;  
    ngx_uint_t        i, n, pages;  
    ngx_slab_page_t  *slots;  
  
    /* STUB */  
    if (ngx_slab_max_size == 0) {  
        // 最大分配空間為頁大小的一半  
        ngx_slab_max_size = ngx_pagesize / 2;  
        // 精確分配大小,8為一個字節的位數,sizeof(uintptr_t)為一個uintptr_t的字節,我們后面會根據這個size來判斷使用不同的分配算法  
        ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));  
        // 計算出此精確分配的移位數  
        for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {  
            /* void */  
        }  
    }  
    /**/  
  
    pool->min_size = 1 << pool->min_shift;  
  
    // p 指向slot數組  
    p = (u_char *) pool + sizeof(ngx_slab_pool_t);  
    size = pool->end - p;  
  
    // 將開始的size個字節設置為0  
    ngx_slab_junk(p, size);  
  
    // 某一個大小范圍內的頁,放到一起,具有相同的移位數  
    slots = (ngx_slab_page_t *) p;  
    // 最大移位數,減去最小移位數,得到需要的slot數量  
    // 默認為8  
    n = ngx_pagesize_shift - pool->min_shift;  
  
    // 初始化各個slot  
    for (i = 0; i < n; i++) {  
        slots[i].slab = 0;  
        slots[i].next = &slots[i];  
        slots[i].prev = 0;  
    }  
  
    // 指向頁數組  
    p += n * sizeof(ngx_slab_page_t);  
  
    // 計算出當前內存空間可以放下多少個頁,此時的計算沒有進行對齊,在后面會進行調整  
    pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));  
  
    ngx_memzero(p, pages * sizeof(ngx_slab_page_t));  
  
    pool->pages = (ngx_slab_page_t *) p;  
  
    pool->free.prev = 0;  
    pool->free.next = (ngx_slab_page_t *) p;  
  
    pool->pages->slab = pages;  
    pool->pages->next = &pool->free;  
    pool->pages->prev = (uintptr_t) &pool->free;  
  
    // 計算出對齊后的返回內存的地址  
    pool->start = (u_char *)  
                  ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t),  
                                 ngx_pagesize);  
  
    // 用於判斷我們對齊后的空間,是否需要進行調整  
    m = pages - (pool->end - pool->start) / ngx_pagesize;  
    // 說明之前是沒有對齊過的,由於對齊之后,最后那一頁,有可能不夠一頁,所以要去掉那一塊  
    if (m > 0) {  
        pages -= m;  
        pool->pages->slab = pages;  
    }  
  
    pool->log_ctx = &pool->zero;  
    pool->zero = '\0';
}  
void *  
ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)  
{  
    size_t            s;  
    uintptr_t         p, n, m, mask, *bitmap;  
    ngx_uint_t        i, slot, shift, map;  
    ngx_slab_page_t  *page, *prev, *slots;  
  
    // 如果超出slab最大可分配大小,即大於2048,則我們需要計算出需要的page數,  
    // 然后從空閑頁中分配出連續的幾個可用頁  
    if (size >= ngx_slab_max_size) {  
  
        // 計算需要的頁數,然后分配指針頁數  
        page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1)  
                                          >> ngx_pagesize_shift);  
        if (page) {  
            // 由返回page在頁數組中的偏移量,計算出實際數組地址的偏移量  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            // 計算出實際的數據地址  
            p += (uintptr_t) pool->start;  
  
        } else {  
            p = 0;  
        }  
  
        goto done;  
    }  
  
    // 如果小於2048,則啟用slab分配算法進行分配  
  
    // 計算出此size的移位數以及此size對應的slot以及移位數  
    if (size > pool->min_size) {  
        shift = 1;  
        // 計算移位數  
        for (s = size - 1; s >>= 1; shift++) { /* void */ }  
        // 由移位數得到slot  
        slot = shift - pool->min_shift;  
  
    } else {  
        // 小於最小可分配大小的都放到一個slot里面  
        size = pool->min_size;  
        shift = pool->min_shift;  
        // 因為小於最小分配的,所以就放在第一個slot里面  
        slot = 0;  
    }  
  
    slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t));  
    // 得到當前slot所占用的頁  
    page = slots[slot].next;  
  
    // 找到一個可用空間  
    if (page->next != page) {  
  
        // 分配大小小於128字節時的算法,看不懂的童鞋可以先看等於128字節的情況  
        // 當分配空間小於128字節時,我們不可能用一個int來表示這些塊的占用情況  
        // 此時,我們就需要幾個int了,即一個bitmap數組  
        // 我們此時沒有使用page->slab,而是使用頁數據空間的開始幾個int空間來表示了  
        // 看代碼  
  
        if (shift < ngx_slab_exact_shift) {  
  
            do {  
                // 得到頁數據部分  
                p = (page - pool->pages) << ngx_pagesize_shift;  
                // 頁的開始幾個int大小的空間來存放位圖數據  
                bitmap = (uintptr_t *) (pool->start + p);  
  
                // 當前頁,在當前size下可分成map*32個塊  
                // 我們需要map個int來表示這些塊空間  
                map = (1 << (ngx_pagesize_shift - shift))  
                          / (sizeof(uintptr_t) * 8);  
  
                for (n = 0; n < map; n++) {  
  
                    if (bitmap[n] != NGX_SLAB_BUSY) {  
  
                        for (m = 1, i = 0; m; m <<= 1, i++) {  
                            if ((bitmap[n] & m)) {  
                                // 當前位表示的塊已被使用了  
                                continue;  
                            }  
  
                            // 設置已占用  
                            bitmap[n] |= m;  
  
                            i = ((n * sizeof(uintptr_t) * 8 ) << shift)  
                                + (i << shift);  
  
                            // 如果當前bitmap所表示的空間已都被占用,就查找下一個bitmap  
                            if (bitmap[n] == NGX_SLAB_BUSY) {  
                                for (n = n + 1; n < map; n++) {  
                                    // 找到下一個還剩下空間的bitmap  
                                     if (bitmap[n] != NGX_SLAB_BUSY) {  
                                         p = (uintptr_t) bitmap + i;  
  
                                         goto done;  
                                     }  
                                }  
  
                                // 剩下所有的bitmap都被占用了,表明當前的頁已完全被使用了,把當前頁從鏈表中刪除  
                                prev = (ngx_slab_page_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                                prev->next = page->next;  
                                page->next->prev = page->prev;  
  
                                page->next = NULL;  
                                // 小內存分配  
                                page->prev = NGX_SLAB_SMALL;  
                            }  
  
                            p = (uintptr_t) bitmap + i;  
  
                            goto done;  
                        }  
                    }  
                }  
  
                page = page->next;  
  
            } while (page);  
  
        } else if (shift == ngx_slab_exact_shift) {  
            // 如果分配大小正好是128字節,則一頁可以分成32個塊,我們可以用一個int來表示這些個塊的使用情況  
            // 這里我們使用page->slab來表示這些塊的使用情況,當所有塊被占用后,該值就變成了0xffffffff,即NGX_SLAB_BUSY  
            // 表示該塊都被占用了  
  
            do {  
                // 當前頁可用  
                if (page->slab != NGX_SLAB_BUSY) {  
  
                    for (m = 1, i = 0; m; m <<= 1, i++) {  
                        // 如果當前位被使用了,就繼續查找下一塊  
                        if ((page->slab & m)) {  
                            continue;  
                        }  
  
                        // 設置當前為已被使用  
                        page->slab |= m;  
  
                        // 最后一塊也被使用了,就表示此頁已使用完  
                        if (page->slab == NGX_SLAB_BUSY) {  
                            // 將當前頁從鏈表中移除  
                            prev = (ngx_slab_page_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                            prev->next = page->next;  
                            page->next->prev = page->prev;  
  
                            page->next = NULL;  
                            // 標識使用類型,精確  
                            page->prev = NGX_SLAB_EXACT;  
                        }  
  
                        p = (page - pool->pages) << ngx_pagesize_shift;  
                        p += i << shift;  
                        p += (uintptr_t) pool->start;  
  
                        goto done;  
                    }  
                }  
  
                // 查找下一頁  
                page = page->next;  
  
            } while (page);  
  
        } else { /* shift > ngx_slab_exact_shift */  
            // 當需要分配的空間大於128時,我們可以用一個int的位來表示這些空間  
            //所以我們依然采用跟等於128時類似的情況,用page->slab來表示  
            // 但由於 大於128的情況比較多,移位數分別為8、9、10、11這些情況  
            // 對於一個頁,我們如何來知道這個頁的分配大小呢?  
            // 而我們知道,最小我們只需要使用16位即可表示這些空間了,即分配大小為256~512時  
            // 那么我們采用高16位來表示這些空間的占用情況  
            // 而最低位,我們也利用起來,表示此頁的分配大小,即保存移位數  
            // 比如我們分配256,當分配第一個空間時,此時的page->slab位圖情況是:0x0001008  
            // 那分配下一空間就是0x0003008了,當為0xffff008時,就分配完了  
            // 看代碼  
  
            // page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其實就是當前頁的分配大小的移位數  
            // ngx_pagesize_shift減掉后,就是在一頁中標記這些塊所需要的移位數,也就是塊數對應的移位數  
            n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK);  
            // 得到一個頁面所能放下的塊數  
            n = 1 << n;  
            // 得到表示這些塊數都用完的bitmap,用現在是低16位的  
            n = ((uintptr_t) 1 << n) - 1;  
            // 將低16位轉換成高16位,因為我們是用高16位來表示空間地址的占用情況的  
            mask = n << NGX_SLAB_MAP_SHIFT;  
  
            do {  
                // 判斷高16位是否全被占用了  
                if ((page->slab & NGX_SLAB_MAP_MASK) != mask) {  
  
                    // NGX_SLAB_MAP_SHIFT 為移位偏移, 得到0x10000  
                    for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;  
                         m & mask;  
                         m <<= 1, i++)  
                    {  
                        // 當前塊是否被占用  
                        if ((page->slab & m)) {  
                            continue;  
                        }  
  
                        // 將當前位設置成1  
                        page->slab |= m;  
  
                        // 當前頁是否完全被占用完  
                        if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {  
                            prev = (ngx_slab_page_t *)  
                                            (page->prev & ~NGX_SLAB_PAGE_MASK);  
                            prev->next = page->next;  
                            page->next->prev = page->prev;  
  
                            page->next = NULL;  
                            page->prev = NGX_SLAB_BIG;  
                        }  
  
                        p = (page - pool->pages) << ngx_pagesize_shift;  
                        p += i << shift;  
                        p += (uintptr_t) pool->start;  
  
                        goto done;  
                    }  
                }  
  
                page = page->next;  
  
            } while (page);  
        }  
    }  
  
    // 如果當前slab對應的page中沒有空間可分配了,則重新從空閑page中分配一個頁  
    page = ngx_slab_alloc_pages(pool, 1);  
  
    if (page) {  
        if (shift < ngx_slab_exact_shift) {  
            // 小於128時  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            bitmap = (uintptr_t *) (pool->start + p);  
  
            // 需要的空間大小  
            s = 1 << shift;  
            n = (1 << (ngx_pagesize_shift - shift)) / 8 / s;  
  
            if (n == 0) {  
                n = 1;  
            }  
  
            bitmap[0] = (2 << n) - 1;  
  
            // 需要使用的  
            map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);  
  
            for (i = 1; i < map; i++) {  
                bitmap[i] = 0;  
            }  
  
            page->slab = shift;  
            page->next = &slots[slot];  
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;  
  
            slots[slot].next = page;  
  
            p = ((page - pool->pages) << ngx_pagesize_shift) + s * n;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
  
        } else if (shift == ngx_slab_exact_shift) {  
  
            // 第一塊空間被占用  
            page->slab = 1;  
            page->next = &slots[slot];  
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;  
  
            slots[slot].next = page;  
  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
  
        } else { /* shift > ngx_slab_exact_shift */  
  
            // 低位表示存放數據的大小  
            page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;  
            page->next = &slots[slot];  
            page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;  
  
            slots[slot].next = page;  
  
            p = (page - pool->pages) << ngx_pagesize_shift;  
            p += (uintptr_t) pool->start;  
  
            goto done;  
        }  
    }  
  
    p = 0;  
  
done:  
  
    return (void *) p;  
}  
/ 分配指針個數的頁  
static ngx_slab_page_t *  
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)  
{  
    ngx_slab_page_t  *page, *p;  
  
    for (page = pool->free.next; page != &pool->free; page = page->next) {  
  
        if (page->slab >= pages) {  
  
            // 當前可以分配  
            if (page->slab > pages) {  
                // 空閑的pages大於需要分配的pages  
  
                // 減少  
                page[pages].slab = page->slab - pages;  
                // 這幾個頁就分配出去了哦!  
                page[pages].next = page->next;  
                page[pages].prev = page->prev;  
  
                p = (ngx_slab_page_t *) page->prev;  
                p->next = &page[pages];  
                page->next->prev = (uintptr_t) &page[pages];  
  
            } else {  
                // slab == pages  
                // 正好分配完  
  
                // 設置free鏈表  
                p = (ngx_slab_page_t *) page->prev;  
                p->next = page->next;  
                page->next->prev = page->prev;  
            }  
  
            page->slab = pages | NGX_SLAB_PAGE_START;  
            page->next = NULL;  
            page->prev = NGX_SLAB_PAGE;  
  
            if (--pages == 0) {  
                return page;  
            }  
  
            for (p = page + 1; pages; pages--) {  
                p->slab = NGX_SLAB_PAGE_BUSY;  
                p->next = NULL;  
                p->prev = NGX_SLAB_PAGE;  
                p++;  
            }  
  
            return page;  
        }  
    }  
  
    return NULL;  
}  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM