本来这一篇作为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 }