本來這一篇作為nginx系列的開頭是不合適的,不過由於nginx進程框架自己的梳理還沒
完成,這部分又剛好整理完了,就從這開始吧。
這兒談的是nginx的slab的內存管理方式,這種方式的內存管理在nginx中,主要是與nginx
的共享內存協同使用的。nginx的slab管理與linux的slab管理相同的地方在於均是利用了內存
的緩存與對齊機制,slab內存管理中一些設計相當巧妙的地方,也有一些地方個人感覺設計
不是很完美,或許是作為nginx設計綜合考慮的結果。
nginx slab實現中的一大特色就是大量的位操作,這部分操作很多是與slot分級數組相關的。
為方便描述下面做一些說明:
1.將整個slab的管理結構稱slab pool.
2.將slab pool前部的ngx_slab_pool_t結構稱slab header.
3.將管理內存分級的ngx_slab_page_t結構稱slot分級數組.
4.將管理page頁使用的ngx_slab_page_t結構稱slab page管理結構.
5.將具體page頁的存放位置稱pages數組.
6.將把page分成的各個小塊稱為chunk.
7.將標記chunk使用的位圖結構稱為bitmap.
整個slab pool中有兩個非常重要的結構,一是ngx_slab_pool_t,即slab header,如下示:
typedef struct { ngx_atomic_t lock; 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;
其中最為重要的幾個成員為:
min_size:指最小分割成的chunk的大小。
min_shift:指min_size對應的移位數。
*pages:指向slab page管理結構的開始位置。
free:空閑的slab page管理結構鏈表。
*start:pages數組的的起始位置。
*end:整個slab pool 的結束位置。
*addr:整個slab pool的開始位置。
另一個重要的結構是ngx_slab_page_t,slot分級數組與slab page管理結構都使用了這個結構,
如下示:
struct ngx_slab_page_s { uintptr_t slab; ngx_slab_page_t *next; uintptr_t prev; };
其中的成員說明如下示:
slab:slab為使用較為復雜的一個字段,有以下四種使用情況
a.存儲為些結構相連的pages的數目(slab page管理結構)
b.存儲標記chunk使用情況的bitmap(size = exact_size)
c.存儲chunk的大小(size < exact_size)
d.存儲標記chunk的使用情況及chunk大小(size > exact_size)
next:使用情況也會分情況處理,后面看。
prev:通常是組合使用在存儲前一個位置及標記page的類型。
先來看下整個slab pool的結構,如下圖示:
上面需要注意的幾個地方:
1.由於內存對齊可能會導致slab pool中有部分未使用的內存區域。
2.由於內存對齊可能會導致pages數組的大小小於slab page管理結構的大小。
3.對於未使用的page管理鏈表其結點非常特殊,可以是由ngx_slab_page_t的數組構成
也可以是單個的ngx_slab_page_t.
4.pages數組中的page是與slab page管理結構一一對應的,雖然slab page有多的。
然后就是一些初始化數據,這兒僅考慮32位的情況。
ngx_pagesize = 4096;
ngx_pagesize_shift = 12;
ngx_slab_max_size = 2048;
ngx_slab_exact_size = 128;
ngx_slab_exact_shift = 7;
ngx_slab_min_size = 128;
ngx_slab_min_shift = 3;
先看slab 內存管理的初始化過程,具體的代碼分析如下:
//slab空間的初始化函數 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 最大slab size的初始化*/ if (ngx_slab_max_size == 0) { ngx_slab_max_size = ngx_pagesize / 2; ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t)); for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) { /* void */ } } /**/ //計算最小的slab大小 pool->min_size = 1 << pool->min_shift; //跳過ngx_slab_page_t的空間,也即跳過slab header p = (u_char *) pool + sizeof(ngx_slab_pool_t); size = pool->end - p; //計算剩余可用空間的大小 ngx_slab_junk(p, size); //進行slot分級數組的初始化 slots = (ngx_slab_page_t *) p; n = ngx_pagesize_shift - pool->min_shift; //計算可分的級數,page_size為4kb時對應的shift為12,若 //最小可為8B,則shift為3,則對應可分為12-3,即8,16,32,64, //128,256,512,1024,2048 9個分級。 for (i = 0; i < n; i++) { slots[i].slab = 0; slots[i].next = &slots[i]; //對應將每個next均初始化為自己 slots[i].prev = 0; } //跳過slot分級數組區域 p += n * sizeof(ngx_slab_page_t); //由於每一個page均對應一個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)); //初始化pages指針的位置 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); //這個地方是進行對齊后的page調整,這個地方是我前面疑問的部分解決位置。 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'; }
然后是內存申請過程:
step 1:根據申請size的大小,判斷申請內存的方式:
case 1:若大於ngx_slab_max_size則直接彩分配page的方式。
調用ngx_slab_alloc_pages后跳轉至step 5.
case 2:若小於等於ngx_slab_max_size則根據size計算分級的級數。
轉step 2.
step 2:檢查計算出的分級數對應的slot分級數組中是否存在可使用的頁,
若存在則轉step 3,否則轉step 4.
step 3:根據size的大小,進行不同的內存分配過程:
case 1:size小於ngx_slab_exact_size時
(1)遍歷bitmap數組查找可用的chunk位置
(2)完成chunk的標記
(3)標記完成后檢查chunk是否是對應的bitmap的最后一個被使用的,
若是,則進步檢查page中是否還存在未使用的chunk,若不存在則
將page脫離出此slot分級數組的管理,標記page的類型為NGX_SLAB_SMALL.
(4)計算申請到的chunk的內存起始地址,轉至step 5.
case 2:size等於ngx_slab_exact_size時
(1)檢查slab字段查找可用的chunk位置
(2)同上
(3)同上,不過page類型標記為NGX_SLAB_EXACT
(4)同上
case 3:size大於ngx_slab_exact_size時
(1)從slab字段中提取出標記chunk使用的bitmap
(2)同case 1 (1)
(3)同case 2 (2)
(4)同case 1 (3),不過page類型標記為NGX_SLAB_BIG
(5)同case 1 (4)
step 4:調用ngx_slab_alloc_pages申請1頁page,然后根據size情況完成page划分
及bitmap的初始化標記。
case 1:小於ngx_slab_exact_size時
(1)計算需要使用的bitmap的個數
(2)完成bitmap使用chunk的標記,同時標記即將分配出去的chunk
(3)完成剩余的bitmap的初始化
(4)設置page的類型為NGX_SLAB_SMALL
(5)計算分配chunk的位置
case 2:等於ngx_slab_exact_size時
(1)完成即將分配的chunk的標記
(2)設置page的類型為NGX_SLAB_EXACT
(3)計算分配的chunk的位置
case 3:
(1)完成chunk的標記,及將chunk的大小同時存儲於slab字段中
(2)設置page的類型為NGX_SLAB_BIG
(3)計算分配的chunk的位置
完成以上情況處理后,跳至step 5
step 5:返回得到空間。
下面看具體的代碼:

1 void * 2 ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size) 3 { 4 size_t s; 5 uintptr_t p, n, m, mask, *bitmap; 6 ngx_uint_t i, slot, shift, map; 7 ngx_slab_page_t *page, *prev, *slots; 8 //case 1:請求的size大於最大的slot的大小,直接以頁的形式分配空間。 9 if (size >= ngx_slab_max_size) { 10 11 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, 12 "slab alloc: %uz", size); 13 //獲取page 14 page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1) 15 >> ngx_pagesize_shift); 16 if (page) { 17 //計算具體的page的位置 18 p = (page - pool->pages) << ngx_pagesize_shift; 19 p += (uintptr_t) pool->start; 20 21 } else { 22 p = 0; 23 } 24 25 goto done; 26 } 27 //case 2:請求的size小於等於2048,可用slot滿足請求 28 if (size > pool->min_size) { 29 shift = 1; 30 for (s = size - 1; s >>= 1; shift++) { /* void */ } 31 slot = shift - pool->min_shift; 32 33 } else { 34 size = pool->min_size; 35 shift = pool->min_shift; 36 slot = 0; 37 } 38 39 ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, 40 "slab alloc: %uz slot: %ui", size, slot); 41 //取得分級數組的起始位置 42 slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); 43 //獲取對應的slot的用於取chunk的頁 44 page = slots[slot].next; 45 //存在用於切割chunk的頁 46 if (page->next != page) { 47 //case 2.1:請求的大小小於可exact切割的chunk大小,即128,需要占用page中前面的chunk來作為chunk使用狀況的位圖 48 if (shift < ngx_slab_exact_shift) { 49 50 do { 51 //計算具體的page頁的存放位置 52 p = (page - pool->pages) << ngx_pagesize_shift; 53 //得到bitmap起始存放的位置 54 bitmap = (uintptr_t *) (pool->start + p); 55 //計算對應shift大小的bitmap的個數 56 map = (1 << (ngx_pagesize_shift - shift)) 57 / (sizeof(uintptr_t) * 8); 58 59 for (n = 0; n < map; n++) { 60 //查找未使用的chunk. 61 if (bitmap[n] != NGX_SLAB_BUSY) { 62 //依次檢查bitmap的各位,以得到未使用的chunk. 63 for (m = 1, i = 0; m; m <<= 1, i++) { 64 if ((bitmap[n] & m)) { 65 continue; 66 } 67 //置使用標記 68 bitmap[n] |= m; 69 //計算找到的chunk的偏移位置。 70 i = ((n * sizeof(uintptr_t) * 8) << shift) 71 + (i << shift); 72 //當每個bitmap標示的chunk剛好使用完時,都會去檢查是否還有chunk未使用 73 //若chunk全部使用完,則將當前的page脫離下來。 74 if (bitmap[n] == NGX_SLAB_BUSY) { 75 for (n = n + 1; n < map; n++) { 76 if (bitmap[n] != NGX_SLAB_BUSY) { 77 //確認了還有未使用的chunk直接返回。 78 p = (uintptr_t) bitmap + i; 79 80 goto done; 81 } 82 } 83 //page頁中的chunk都使用完了,將page脫離 84 //與NGX_SLAB_PAGE_MASK的反&運算是為了去除之前設置的page類型標記以得到prev的地址。 85 prev = (ngx_slab_page_t *) 86 (page->prev & ~NGX_SLAB_PAGE_MASK); 87 prev->next = page->next; 88 page->next->prev = page->prev; 89 90 page->next = NULL; 91 //置chunk的類型 92 page->prev = NGX_SLAB_SMALL; 93 } 94 95 p = (uintptr_t) bitmap + i; 96 97 goto done; 98 } 99 } 100 } 101 102 page = page->next; 103 104 } while (page); 105 106 } else if (shift == ngx_slab_exact_shift) { 107 //請求的大小剛好為exact的大小,即128,這時slab僅做bitmap使用 108 do { 109 //直接比較slab看有空的chunk不。 110 if (page->slab != NGX_SLAB_BUSY) { 111 //逐位比較,查找chunk. 112 for (m = 1, i = 0; m; m <<= 1, i++) { 113 if ((page->slab & m)) { 114 continue; 115 } 116 //置使用標記 117 page->slab |= m; 118 //檢查page中的chunk是否使用完,使用完則做脫離處理 119 if (page->slab == NGX_SLAB_BUSY) { 120 prev = (ngx_slab_page_t *) 121 (page->prev & ~NGX_SLAB_PAGE_MASK); 122 prev->next = page->next; 123 page->next->prev = page->prev; 124 125 page->next = NULL; 126 page->prev = NGX_SLAB_EXACT; 127 } 128 129 p = (page - pool->pages) << ngx_pagesize_shift; 130 p += i << shift; 131 p += (uintptr_t) pool->start; 132 133 goto done; 134 } 135 } 136 137 page = page->next; 138 139 } while (page); 140 141 } else { /* shift > ngx_slab_exact_shift */ 142 //case 2.3:申請的size大小128,但小於等於2048時。 143 //此時的slab同時存儲bitmap及表示chunk大小的shift,高位為bitmap. 144 //獲取bitmap的高位mask. 145 n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK); 146 n = 1 << n; 147 n = ((uintptr_t) 1 << n) - 1; 148 mask = n << NGX_SLAB_MAP_SHIFT; 149 150 do { 151 if ((page->slab & NGX_SLAB_MAP_MASK) != mask) { 152 //逐位查找空的chunk. 153 for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0; 154 m & mask; 155 m <<= 1, i++) 156 { 157 if ((page->slab & m)) { 158 continue; 159 } 160 161 page->slab |= m; 162 //檢查page中的chunk是否使用完 163 if ((page->slab & NGX_SLAB_MAP_MASK) == mask) { 164 prev = (ngx_slab_page_t *) 165 (page->prev & ~NGX_SLAB_PAGE_MASK); 166 prev->next = page->next; 167 page->next->prev = page->prev; 168 169 page->next = NULL; 170 page->prev = NGX_SLAB_BIG; 171 } 172 173 p = (page - pool->pages) << ngx_pagesize_shift; 174 p += i << shift; 175 p += (uintptr_t) pool->start; 176 177 goto done; 178 } 179 } 180 181 page = page->next; 182 183 } while (page); 184 } 185 } 186 187 page = ngx_slab_alloc_pages(pool, 1); 188 189 if (page) { 190 if (shift < ngx_slab_exact_shift) { 191 //page用於小於128的chunk時, 192 //獲取page的存放位置 193 p = (page - pool->pages) << ngx_pagesize_shift; 194 //獲取bitmap的開始位置 195 bitmap = (uintptr_t *) (pool->start + p); 196 197 s = 1 << shift; 198 //計算chunk的個數 199 n = (1 << (ngx_pagesize_shift - shift)) / 8 / s; 200 201 if (n == 0) { 202 n = 1; 203 } 204 //給bitmap占用的chunk置標記,同時對將要使用的chunk進行標記。 205 bitmap[0] = (2 << n) - 1; 206 //計算要使用的bitmap的個數 207 map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); 208 //從第二個開始初始化bitmap. 209 for (i = 1; i < map; i++) { 210 bitmap[i] = 0; 211 } 212 213 page->slab = shift; 214 page->next = &slots[slot]; 215 //設置page的類型,低位存儲 216 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; 217 218 slots[slot].next = page; 219 //計算申請的chunk的位置。 220 p = ((page - pool->pages) << ngx_pagesize_shift) + s * n; 221 p += (uintptr_t) pool->start; 222 223 goto done; 224 225 } else if (shift == ngx_slab_exact_shift) { 226 //chunk的大小正好為128時,此時處理很簡單 227 page->slab = 1; 228 page->next = &slots[slot]; 229 //置chunk類型 230 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; 231 232 slots[slot].next = page; 233 234 p = (page - pool->pages) << ngx_pagesize_shift; 235 p += (uintptr_t) pool->start; 236 237 goto done; 238 239 } else { /* shift > ngx_slab_exact_shift */ 240 //大於128時,slab要存儲bitmap及表示chunk大小的shift. 241 page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift; 242 page->next = &slots[slot]; 243 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; 244 245 slots[slot].next = page; 246 247 p = (page - pool->pages) << ngx_pagesize_shift; 248 p += (uintptr_t) pool->start; 249 250 goto done; 251 } 252 } 253 254 p = 0; 255 256 done: 257 258 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab alloc: %p", p); 259 260 return (void *) p; 261 }
然后補充下,ngx_slab_alloc_pages函數的代碼分析:

1 //這個函數是用來申請page的,此函數的實現也為nginx slab在申請大內存的處理時留下了隱患。 2 static ngx_slab_page_t * 3 ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages) 4 { 5 ngx_slab_page_t *page, *p; 6 //在slab page的管理頁中查找,找到slab管理塊中能夠一次滿足要求的slab,這和slab page的管理時不合並有關 7 for (page = pool->free.next; page != &pool->free; page = page->next) { 8 //找到了合適的slab 9 if (page->slab >= pages) { 10 //對於大於請求page數的情況,會將前pages個切分出去,page[pages]剛好為將 11 //pages個切分出去后,逗留下的第一個,正好作為構建新結點的第一個。下面 12 //其實是一個雙向鏈表插入及刪除節點時的操作 13 if (page->slab > pages) { 14 page[pages].slab = page->slab - pages; 15 page[pages].next = page->next; 16 page[pages].prev = page->prev; 17 18 p = (ngx_slab_page_t *) page->prev; 19 p->next = &page[pages]; 20 page->next->prev = (uintptr_t) &page[pages]; 21 22 } else { 23 //恰好等於時,不用進行切分直接刪除節點 24 p = (ngx_slab_page_t *) page->prev; 25 p->next = page->next; 26 page->next->prev = page->prev; 27 } 28 //修改page對應的狀態 29 page->slab = pages | NGX_SLAB_PAGE_START; 30 page->next = NULL; 31 page->prev = NGX_SLAB_PAGE; 32 33 if (--pages == 0) { 34 return page; 35 } 36 //對於pages大於1的情況,還處理非第一個page的狀態,修改為BUSY 37 for (p = page + 1; pages; pages--) { 38 p->slab = NGX_SLAB_PAGE_BUSY; 39 p->next = NULL; 40 p->prev = NGX_SLAB_PAGE; 41 p++; 42 } 43 44 return page; 45 } 46 } 47 48 ngx_slab_error(pool, NGX_LOG_CRIT, "ngx_slab_alloc() failed: no memory"); 49 50 return NULL; 51 }
下面是slab內存管理機制的釋放過程分析:
step 1:判斷異常情況,得到釋放空間位於的page的slab 管理結構的位置及page的位置。
step 2:獲取釋放空間位於的page的類型,根據類型進行處理:
case 1:NGX_SLAB_SMALL
(1)從slab字段中取出chunk的大小
(2)計算要釋放的空間位於page中的chunk的偏移
(3)計算對應chunk位於bitmap數組中的第幾個bitmap
(4)計算對應bitmap的地址
(5)檢查對應的chunk的bitmap中的標記,若為未釋放標記則進行后面的處理,否則
直接返回已經釋放。
(6)將釋放空間的page重新置於對應分級數組的管理下
(7)置釋放標記,同時檢查頁中管理的chunk是否均為未使用,若全為,則調用
ngx_slab_free_pages進行page的回收,即將其加入slab page的管理之下。
否則返回。
case 2:NGX_SLAB_EXACT
(1)同case 1 (2)
(2)同case 1 (5)
(3)同case 1 (7)
case 3:NGX_SLAB_BIG
(1)從slab字段中提取出bitmap及chunk的大小
(2)同case 1 (2)
(3)同case 1 (5)
(4)同case 1 (7)
case 4:NGX_SLAB_PAGE
好像是處理一些頁相關的釋放情況,不詳細討論
step 3:返回
下面看具體代碼:

1 void 2 ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p) 3 { 4 size_t size; 5 uintptr_t slab, m, *bitmap; 6 ngx_uint_t n, type, slot, shift, map; 7 ngx_slab_page_t *slots, *page; 8 9 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p); 10 //判斷異常情況 11 if ((u_char *) p < pool->start || (u_char *) p > pool->end) { 12 ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool"); 13 goto fail; 14 } 15 //計算釋放的page的偏移 16 n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; 17 //獲取對應slab_page管理結構的位置 18 page = &pool->pages[n]; 19 slab = page->slab; 20 //獲取page的類型 21 type = page->prev & NGX_SLAB_PAGE_MASK; 22 23 switch (type) { 24 //page類型為小於128時。 25 case NGX_SLAB_SMALL: 26 //此時chunk的大小存放於slab中。 27 shift = slab & NGX_SLAB_SHIFT_MASK; 28 //計算chunk的大小 29 size = 1 << shift; 30 31 if ((uintptr_t) p & (size - 1)) { 32 goto wrong_chunk; 33 } 34 //這段特別巧妙,由於前面對頁進行了內存對齊的處理,因此下面的式子可直接 35 //求出p位於的chunk偏移,即是page中的第幾個chunk. 36 n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift; 37 //計算chunk位於bitmap管理的chunk的偏移,注意對2的n次方的取余操作的實現。 38 m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1)); 39 //計算p指向的chunk位於第幾個bitmap中。 40 n /= (sizeof(uintptr_t) * 8); 41 //計算bitmap的起始位置 42 bitmap = (uintptr_t *) ((uintptr_t) p & ~(ngx_pagesize - 1)); 43 //判斷是否處於free狀態。 44 if (bitmap[n] & m) { 45 //將page插入到對應slot分級數組管理的slab鏈表中,位於頭部 46 if (page->next == NULL) { 47 slots = (ngx_slab_page_t *) 48 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 49 slot = shift - pool->min_shift; 50 51 page->next = slots[slot].next; 52 slots[slot].next = page; 53 54 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; 55 page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL; 56 } 57 //置釋放標記 58 bitmap[n] &= ~m; 59 //計算chunk的個數 60 n = (1 << (ngx_pagesize_shift - shift)) / 8 / (1 << shift); 61 62 if (n == 0) { 63 n = 1; 64 } 65 //檢查首個bitmap對bitmap占用chunk的標記情況。 66 if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) { 67 goto done; 68 } 69 //計算bitmap的個數 70 map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); 71 72 for (n = 1; n < map; n++) { 73 if (bitmap[n]) { 74 goto done; 75 } 76 } 77 //如果釋放后page中沒有在使用的chunk,則進行進一步的回收,改用slab_page進行管理 78 ngx_slab_free_pages(pool, page, 1); 79 80 goto done; 81 } 82 83 goto chunk_already_free; 84 85 case NGX_SLAB_EXACT: 86 87 m = (uintptr_t) 1 << 88 (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift); 89 size = ngx_slab_exact_size; 90 91 if ((uintptr_t) p & (size - 1)) { 92 goto wrong_chunk; 93 } 94 95 if (slab & m) { 96 if (slab == NGX_SLAB_BUSY) { 97 slots = (ngx_slab_page_t *) 98 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 99 slot = ngx_slab_exact_shift - pool->min_shift; 100 101 page->next = slots[slot].next; 102 slots[slot].next = page; 103 104 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; 105 page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT; 106 } 107 108 page->slab &= ~m; 109 110 if (page->slab) { 111 goto done; 112 } 113 114 ngx_slab_free_pages(pool, page, 1); 115 116 goto done; 117 } 118 119 goto chunk_already_free; 120 121 case NGX_SLAB_BIG: 122 123 shift = slab & NGX_SLAB_SHIFT_MASK; 124 size = 1 << shift; 125 126 if ((uintptr_t) p & (size - 1)) { 127 goto wrong_chunk; 128 } 129 130 m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift) 131 + NGX_SLAB_MAP_SHIFT); 132 133 if (slab & m) { 134 135 if (page->next == NULL) { 136 slots = (ngx_slab_page_t *) 137 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 138 slot = shift - pool->min_shift; 139 140 page->next = slots[slot].next; 141 slots[slot].next = page; 142 143 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; 144 page->next->prev = (uintptr_t) page | NGX_SLAB_BIG; 145 } 146 147 page->slab &= ~m; 148 149 if (page->slab & NGX_SLAB_MAP_MASK) { 150 goto done; 151 } 152 153 ngx_slab_free_pages(pool, page, 1); 154 155 goto done; 156 } 157 158 goto chunk_already_free; 159 160 case NGX_SLAB_PAGE: 161 162 if ((uintptr_t) p & (ngx_pagesize - 1)) { 163 goto wrong_chunk; 164 } 165 166 if (slab == NGX_SLAB_PAGE_FREE) { 167 ngx_slab_error(pool, NGX_LOG_ALERT, 168 "ngx_slab_free(): page is already free"); 169 goto fail; 170 } 171 172 if (slab == NGX_SLAB_PAGE_BUSY) { 173 ngx_slab_error(pool, NGX_LOG_ALERT, 174 "ngx_slab_free(): pointer to wrong page"); 175 goto fail; 176 } 177 178 n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; 179 size = slab & ~NGX_SLAB_PAGE_START; 180 181 ngx_slab_free_pages(pool, &pool->pages[n], size); 182 183 ngx_slab_junk(p, size << ngx_pagesize_shift); 184 185 return; 186 } 187 188 /* not reached */ 189 190 return; 191 192 done: 193 194 ngx_slab_junk(p, size); 195 196 return; 197 198 wrong_chunk: 199 200 ngx_slab_error(pool, NGX_LOG_ALERT, 201 "ngx_slab_free(): pointer to wrong chunk"); 202 203 goto fail; 204 205 chunk_already_free: 206 207 ngx_slab_error(pool, NGX_LOG_ALERT, 208 "ngx_slab_free(): chunk is already free"); 209 210 fail: 211 212 return; 213 }
補充ngx_slab_free_pages的代碼分析:

1 static void 2 ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page, 3 ngx_uint_t pages) 4 { 5 ngx_slab_page_t *prev; 6 //計算結點后部跟的page的數目 7 page->slab = pages--; 8 //對跟的page的page管理結構slab_page進行清空。 9 if (pages) { 10 ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t)); 11 } 12 //如果page后面還跟有節點,則將其連接至page的前一個結點。 13 if (page->next) { 14 prev = (ngx_slab_page_t *) (page->prev & ~NGX_SLAB_PAGE_MASK); 15 prev->next = page->next; 16 page->next->prev = page->prev; 17 } 18 //將page重新歸於,slab_page的管理結構之下。放於管理結構的頭部。 19 page->prev = (uintptr_t) &pool->free; 20 page->next = pool->free.next; 21 22 page->next->prev = (uintptr_t) page; 23 24 pool->free.next = page; 25 }
然后再看下slot分級數組對於page的管理:
初始化:初始化時,各個slot將未分配具體的page.
申請時:初始申請時沒有page空間可用,然后會像slab page管理結構申請page空間,完成
page的划分chunk及bitmap的初始后,會分配一塊chunk以供使用。對於再次申請時
會直接從page中取可用的chunk,當page時無可用的chunk時,此page頁會暫時脫離
slot分級數組的管理,即將其從對應鏈表中刪除。
釋放時:釋放時完成空間的回收標記后,會將page插入到對應的slot管理鏈表的頭部,然后
會進一步檢查些page是否全部chunk均未使用,若是,則進步回收此page將其置於
slab page管理結構的管理之下。
具體如下圖示:
BEGIN:
AFTER:
..
最后就是對於slab管理機制對於使用一段時間后,對於大內存申請的處理會大概率返回失敗
的情況分析。
主要原因在於ngx_slab_free_pages函數里面,從函數中看出,每次回收頁到slab page的管理
結構中時只會對page進行加入鏈表的操作,而沒有如同伙伴算法的結點合並操作,這樣經由
ngx_slab_alloc_pages申請大內存時,在查找一個結點擁有符合要求的page數目時,將不能
得到一個滿足要求的節點,因為使用一段時間后,可能slab page管理結構中的各個結點均會
成為小的數目page的組合。導致申請大的內存失敗。