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; }