原本這一篇做爲nginx系列的開頭是不合適的,不過因爲nginx進程框架本身的梳理還沒
完成,這部分又恰好整理完了,就從這開始吧。
這兒談的是nginx的slab的內存管理方式,這種方式的內存管理在nginx中,主要是與nginx
的共享內存協同使用的。nginx的slab管理與linux的slab管理相同的地方在於均是利用了內存
的緩存與對齊機制,slab內存管理中一些設計至關巧妙的地方,也有一些地方我的感受設計
不是很完美,或許是做爲nginx設計綜合考慮的結果。
nginx slab實現中的一大特點就是大量的位操做,這部分操做不少是與slot分級數組相關的。html
爲方便描述下面作一些說明:
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.linux
整個slab pool中有兩個很是重要的結構,一是ngx_slab_pool_t,即slab header,以下示:nginx
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;ide
先看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:返回獲得空間。oop
下面看具體的代碼:
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的組合。致使申請大的內存失敗。