本来这一篇作为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的组合。导致申请大的内存失败。