Nginx源码分析(2)之——共享内存管理之slab机制
2017-01-10 14:32
861 查看
Refer:
《深入剖析Nginx》 Chapter 3.5 共享内存
《深入理解Nginx–模块开发与架构解析》 Chapter 16 slab共享内存
Nginx源码版本:
nginx-1.10.1
下面直接分析源代码,在代码里进行注释:
参考
Nginx内存管理及数据结构浅析–内存池
nginx 内存池分析
nginx slab内存管理
内存池到底为我们解决了什么问题
C++ 应用程序性能优化,第 6 章:内存池
《深入剖析Nginx》 Chapter 3.5 共享内存
《深入理解Nginx–模块开发与架构解析》 Chapter 16 slab共享内存
Nginx源码版本:
nginx-1.10.1
下面直接分析源代码,在代码里进行注释:
-------------------------------- nginx-1.10.1/src/core/ngx_slab.h -------------------------------- 18 struct ngx_slab_page_s { 19 uintptr_t slab; 20 ngx_slab_page_t *next; 21 uintptr_t prev; 22 }; 23 24 25 typedef struct { 26 ngx_shmtx_sh_t lock; 27 28 size_t min_size; 29 size_t min_shift; 30 31 ngx_slab_page_t *pages; 32 ngx_slab_page_t *last; 33 ngx_slab_page_t free; 34 35 u_char *start; 36 u_char *end; 37 38 ngx_shmtx_t mutex; 39 40 u_char *log_ctx; 41 u_char zero; 42 43 unsigned log_nomem:1; 44 45 void *data; 46 void *addr; 47 } ngx_slab_pool_t;
-------------------------------- nginx-1.10.1/src/core/ngx_slab.c -------------------------------- 72 void 73 ngx_slab_init(ngx_slab_pool_t *pool) 74 { 75 u_char *p; 76 size_t size; 77 ngx_int_t m; 78 ngx_uint_t i, n, pages; 79 ngx_slab_page_t *slots; 80 81 /* STUB */ 82 if (ngx_slab_max_size == 0) { 83 ngx_slab_max_size = ngx_pagesize / 2; 84 ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t)); 85 for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) { 86 /* void */ 87 } 88 } 89 /**/ 90 91 pool->min_size = 1 << pool->min_shift; 92 93 p = (u_char *) pool + sizeof(ngx_slab_pool_t); 94 size = pool->end - p; 95 96 ngx_slab_junk(p, size); 97 98 slots = (ngx_slab_page_t *) p; 99 n = ngx_pagesize_shift - pool->min_shift; 100 101 for (i = 0; i < n; i++) { 102 slots[i].slab = 0; 103 slots[i].next = &slots[i]; 104 slots[i].prev = 0; 105 } 106 107 p += n * sizeof(ngx_slab_page_t); 108 109 pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t))); 110 111 ngx_memzero(p, pages * sizeof(ngx_slab_page_t)); 112 113 pool->pages = (ngx_slab_page_t *) p; 114 115 pool->free.prev = 0; 116 pool->free.next = (ngx_slab_page_t *) p; 117 118 pool->pages->slab = pages; 119 pool->pages->next = &pool->free; 120 pool->pages->prev = (uintptr_t) &pool->free; 121 122 pool->start = (u_char *) 123 ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t), 124 ngx_pagesize); 125 126 m = pages - (pool->end - pool->start) / ngx_pagesize; 127 if (m > 0) { 128 pages -= m; 129 pool->pages->slab = pages; 130 } 131 132 pool->last = pool->pages + pages; 133 134 pool->log_nomem = 1; 135 pool->log_ctx = &pool->zero; 136 pool->zero = '\0'; 137 } 155 void * 156 ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size) 157 { 158 size_t s; 159 uintptr_t p, n, m, mask, *bitmap; 160 ngx_uint_t i, slot, shift, map; 161 ngx_slab_page_t *page, *prev, *slots; 162 /* * slab 中把不等长的内存大小分为4个大类: * 1、小块内存(NGX_SLAB_SMALL): 内存大小 < ngx_slab_exact_size * 2、中等内存(NGX_SLAB_EXACT): 内存大小 == ngx_slab_exact_size * 3、大块内存(NGX_SLAB_BIG ): ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size * 4、超大内存(NGX_SLAB_PAGE ): ngx_slab_max_size < 内存大小 * * ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t)); * ngx_slab_exact_size 表示 uintptr_t slab; 变量当作bitmap使用来表示一页内存中内存块的使用状况时, * slab所有的位(8 * sizeof(uintptr_t))正好不多不少,可以对应到一页内存里所有的内存块时,该页内存该分配 * 成多大的等长内存块 * 一般情况下,ngx_pagesize = getpagesize(); = 4096 byte, 所以,ngx_slab_exact_size = 64 byte */ // 4、超大内存,超出slab最大可分配大小,即大于2048,则需要计算出需要的page数 163 if (size > ngx_slab_max_size) { 164 165 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, 166 "slab alloc: %uz", size); 167 /* * (size >> ngx_pagesize_shift) + ((size % ngx_pagesize) ? 1 : 0) * 表示要分配多少内存页才能满足超大内存当需求 * 从空闲页中分配出连续的几个可用页面 * 返回的是连续可用页面首页对应的管理结构:ngx_slab_page_t结构,并非真实可用内存的实际对应首地址 */ 168 page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift) 169 + ((size % ngx_pagesize) ? 1 : 0)); 170 if (page) { /* * 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量 * 再加上真实可用内存的pool->start即本次分配的真实可用内存的实际对应首地址 * 至此,超大内存分配完成,goto done,返回 */ 171 p = (page - pool->pages) << ngx_pagesize_shift; 172 p += (uintptr_t) pool->start; 173 174 } else { 175 p = 0; 176 } 177 178 goto done; 179 } 180 // 如果小于等于2048,则启用slab分配算法进行分配 // 计算出此size的移位数以及此size对应的slot以及移位数 181 if (size > pool->min_size) { 182 shift = 1; /* * 计算移位数, 并由移位数得到slot * 例如:size = 10(pool->min_size = 8), 最后 shift = 4,则 slot = 4 - 3 = 1 * 0 < 内存大小 <= 8 占据 slot[0] * 8 < 内存大小 <= 16 占据 slot[1] * shift = 4, slot = 1, 符合预期 */ 183 for (s = size - 1; s >>= 1; shift++) { /* void */ } 184 slot = shift - pool->min_shift; 185 186 } else { /* * 小于最小可分配大小的都放到slot[0]里面, 即小于最小可分配大小的内存需求都直接分配最小可分配内存(这里为8byte) * shift = 3, slot = 0, 符合预期 */ 187 size = pool->min_size; 188 shift = pool->min_shift; 189 slot = 0; 190 } 191 192 ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, 193 "slab alloc: %uz slot: %ui", size, slot); 194 /* * 跳到当前合适页所在的slot数组元素的首个元素 */ 195 slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t)); 196 page = slots[slot].next; 197 198 if (page->next != page) { 199 /* * 1、小块内存 * * 当从一个页中分配大小小于ngx_slab_exact_shift(ngx_slab_exact_shift = 64)的内存块时 * 无法用uintptr_t slab;来标识一页内所有内存块的使用情况,因此, * 这里不用 page->slab 来标识该页内所有内存块的使用情况,而是 * 使用页数据空间的开始几个uintptr_t空间来表示了 * ngx_slab_exact_size = 64 byte, ngx_slab_exact_shift = 6 */ 200 if (shift < ngx_slab_exact_shift) { 201 202 do { /* * 同上: * 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量 * 再加上真实可用内存的pool->start即本次分配的真实可用内存的实际对应首地址 * 并得到“页数据空间的开始几个int空间”的首地址 */ 203 p = (page - pool->pages) << ngx_pagesize_shift; 204 bitmap = (uintptr_t *) (pool->start + p); 205 /* * 计算出到底需要多少个(map个)int来作为bitmap来标识这些内存块空间的使用情况 * ngx_pagesize = getpagesize(); = 4096 byte, ngx_pagesize_shift = 12 * 如:shift = 3,则:一页(4096byte)可分为512个 8byte内存块,需要 map = 16 个int(16 * 4 * 8 bit)来作为bitmap * shift = 4, 则:一页(4096byte)可分为256个16byte内存块,需要 map = 8 个int( 8 * 4 * 8 bit)来作为bitmap * shift = 5, 则:一页(4096byte)可分为128个32byte内存块,需要 map = 4 个int( 4 * 4 * 8 bit)来作为bitmap */ 206 map = (1 << (ngx_pagesize_shift - shift)) 207 / (sizeof(uintptr_t) * 8); 208 209 for (n = 0; n < map; n++) { 210 /* * #define NGX_SLAB_BUSY 0xffffffffffffffff * 表示该bitmap对应的页内内存块都已经被使用 */ 211 if (bitmap != NGX_SLAB_BUSY) { 212 213 for (m = 1, i = 0; m; m <<= 1, i++) { 214 if ((bitmap & m)) { // 当前位表示的块已被使用了 215 continue; 216 } 217 // 找到了还没有被占用的内存块,设置bitmap位占位 218 bitmap |= m; 219 /* * 每个内存块大小为 1 << shift, * 现在找到了第n个bitmap的第i位所标识的内存块可用 * 因此,计算得到该块内存的偏移 i */ 220 i = ((n * sizeof(uintptr_t) * 8) << shift) 221 + (i << shift); 222 /* * 当当前bitmap可以利用的bit被标识后,并且当前bitmap所对应的内存块都已经被分配完了,则 * 遍历剩下的bitmap位,如果剩下的bitmap都已经被标识,则 * 表示该bitmap数组对应的内存页全部都使用完了,则将当前的page从slot脱离下来(全满页不在任何链表中) */ 223 if (bitmap == NGX_SLAB_BUSY) { 224 for (n = n + 1; n < map; n++) { 225 if (bitmap != NGX_SLAB_BUSY) { /* * 该page并非全满页,返回真实可用内存的实际对应首地址 * 其中,bitmap:真实可用内存page的首地址 * i : 可分配内存块在该page中的偏移 */ 226 p = (uintptr_t) bitmap + i; 227 228 goto done; 229 } 230 } 231 // 分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来 232 prev = (ngx_slab_page_t *) 233 (page->prev & ~NGX_SLAB_PAGE_MASK); 234 prev->next = page->next; 235 page->next->prev = page->prev; 236 237 page->next = NULL; 238 page->prev = NGX_SLAB_SMALL; 239 } 240 // 同上 241 p = (uintptr_t) bitmap + i; 242 243 goto done; 244 } 245 } 246 } 247 248 page = page->next; 249 250 } while (page); // 这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止 251 252 } else if (shift == ngx_slab_exact_shift) { 253 /* * 2、中等内存 的情况 * * 直接用 page->slab 来标识该页内所有内存块的使用情况(刚刚好,bit不多不少) */ 254 do { /* * 同上: * #define NGX_SLAB_BUSY 0xffffffffffffffff * 表示该page内所有的内存块都已经被使用 */ 255 if (page->slab != NGX_SLAB_BUSY) { 256 257 for (m = 1, i = 0; m; m <<= 1, i++) { 258 if ((page->slab & m)) { 259 continue; 260 } 261 // 同上,找到了还没有被占用的内存块,设置bitmap位占位 262 page->slab |= m; 263 // 同上,分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来 264 if (page->slab == NGX_SLAB_BUSY) { 265 prev = (ngx_slab_page_t *) 266 (page->prev & ~NGX_SLAB_PAGE_MASK); 267 prev->next = page->next; 268 page->next->prev = page->prev; 269 270 page->next = NULL; 271 page->prev = NGX_SLAB_EXACT; 272 } 273 /* * 返回真实可用内存的实际对应首地址 * 其中,(page - pool->pages) << ngx_pagesize_shift:该page对应真实可用内存地址相对于pool->start的偏移 * i << shift : 可分配内存块在该page中的偏移 */ 274 p = (page - pool->pages) << ngx_pagesize_shift; 275 p += i << shift; 276 p += (uintptr_t) pool->start; 277 278 goto done; 279 } 280 } 281 282 page = page->next; 283 284 } while (page); // 同上,这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止 285 286 } else { /* shift > ngx_slab_exact_shift */ 287 /* * 64位系统上 * 64 bytes 2048 bytes * 3、大块内存(ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size) 的情况 * * 当需要分配的空间大于ngx_slab_exact_size = 64 byte时,我们可以用一个int的位来表示这些空间 * 所以我们依然采用跟等于ngx_slab_exact_size时类似的情况,用 page->slab 来标识该page内所有内存块的使用情况 * 此时的page->slab同时存储bitmap及表示内存块大小的shift, 高位为bitmap. * 这里会有内存块大小依次为:128 bytes、256 bytes、512 bytes、1024 bytes、2048 bytes 等的情况 * 对应有 shift依次为: 7 、 8 、 9 、 10 、 11 等 * 那么采用page->slab的高16位来表示这些空间的占用情况,而最低位,则利用起来表示此页的分配大小,即保存移位数 * 例如: * 比如我们分配256,当分配第一个空间时,此时的page->slab位图情况是:0x00010008 * 那分配下一空间就是0x00030008了,当为0xffff0008时,就分配完了 * * #define NGX_SLAB_SHIFT_MASK 0x000000000000000f * page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数 * 这里用最低的一位十六进制表示就足够了,因为shift最大为11(表示内存块大小为2048 bytes) * ngx_pagesize_shift减掉后,就是在一页中标记这些块所需要的移位数,也就是块数对应的移位数 * 例如: * 当页内所能分配的内存块大小为256bytes时,此时,page->slab & NGX_SLAB_SHIFT_MASK = 8 * 因此,n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK) = 12 - 8 = 4 * 即4096 bytes 可以分配16个 256 bytes,因此 n = 1 << n = 16 */ 288 n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK); // 得到一个页面所能放下的块数 289 n = 1 << n; // 得到表示这些块数都用完的bitmap,用现在是低32位的 290 n = ((uintptr_t) 1 << n) - 1; // 将低32位转换成高32位,因为我们是用高32位来表示空间地址的占用情况的,#define NGX_SLAB_MAP_SHIFT 32 291 mask = n << NGX_SLAB_MAP_SHIFT; 292 293 do { // 表示非全满页 294 if ((page->slab & NGX_SLAB_MAP_MASK) != mask) { 295 296 for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0; 297 m & mask; 298 m <<= 1, i++) 299 { 300 if ((page->slab & m)) { 301 continue; 302 } 303 // 同上,找到了还没有被占用的内存块,设置bitmap位占位 304 page->slab |= m; 305 // 同上,分配了相应的内存块后,该page变成了全满页,则把该page从对应的slot数组的链表中摘出来 306 if ((page->slab & NGX_SLAB_MAP_MASK) == mask) { 307 prev = (ngx_slab_page_t *) 308 (page->prev & ~NGX_SLAB_PAGE_MASK); 309 prev->next = page->next; 310 page->next->prev = page->prev; 311 312 page->next = NULL; 313 page->prev = NGX_SLAB_BIG; 314 } 315 /* * 返回真实可用内存的实际对应首地址 * 同上: * 其中,(page - pool->pages) << ngx_pagesize_shift:该page对应真实可用内存地址相对于pool->start的偏移 * i << shift : 可分配内存块在该page中的偏移 */ 316 p = (page - pool->pages) << ngx_pagesize_shift; 317 p += i << shift; 318 p += (uintptr_t) pool->start; 319 320 goto done; 321 } 322 } 323 324 page = page->next; 325 326 } while (page); // 同上,这里表示遍历该slot数据元素,直到找到合适的ngx_slab_page_t页面,或者遍历结束为止 327 } 328 } 329 /* * 在 小块内存、中等内存、大块内存 等三种情况下(不包括超大页面的情况), * 如果当前slab对应的page中没有空间可分配了,则重新从空闲page中分配一个页 */ 330 page = ngx_slab_alloc_pages(pool, 1); 331 332 if (page) { 333 if (shift < ngx_slab_exact_shift) { 334 p = (page - pool->pages) << ngx_pagesize_shift; 335 bitmap = (uintptr_t *) (pool->start + p); 336 /* * 这里shift代表要分配多大内存块的移位数,因此 * s即需要分配内存块的大小 * n表示page会被分成多少个大小为s的内存块 */ 337 s = 1 << shift; 338 n = (1 << (ngx_pagesize_shift - shift)) / 8 / s; 339 340 if (n == 0) { 341 n = 1; 342 } 343 344 bitmap[0] = (2 << n) - 1; 345 // 计算出到底需要多少个(map个)int来作为bitmap来标识这些内存块空间的使用情况 346 map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); 347 // 将剩下的bitmap数组全部初识化为0,除了bitmap[0],前面已经进行过置位了 348 for (i = 1; i < map; i++) { 349 bitmap[i] = 0; 350 } 351 // 在 1、小块内存 中,page->slab存放等长内存块的大小(用位偏移的方式存储) 352 page->slab = shift; 353 page->next = &slots[slot]; 354 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; 355 356 slots[slot].next = page; 357 358 p = ((page - pool->pages) << ngx_pagesize_shift) + s * n; 359 p += (uintptr_t) pool->start; 360 361 goto done; 362 363 } else if (shift == ngx_slab_exact_shift) { 364 365 page->slab = 1; 366 page->next = &slots[slot]; 367 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; 368 369 slots[slot].next = page; 370 371 p = (page - pool->pages) << ngx_pagesize_shift; 372 p += (uintptr_t) pool->start; 373 374 goto done; 375 376 } else { /* shift > ngx_slab_exact_shift */ 377 378 page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift; 379 page->next = &slots[slot]; 380 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; 381 382 slots[slot].next = page; 383 384 p = (page - pool->pages) << ngx_pagesize_shift; 385 p += (uintptr_t) pool->start; 386 387 goto done; 388 } 389 } 390 391 p = 0; 392 393 done: 394 395 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, 396 "slab alloc: %p", (void *) p); 397 398 return (void *) p; 399 } 442 void 443 ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p) 444 { 445 size_t size; 446 uintptr_t slab, m, *bitmap; 447 ngx_uint_t n, type, slot, shift, map; 448 ngx_slab_page_t *slots, *page; 449 450 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p); 451 // 剔除异常情况 452 if ((u_char *) p < pool->start || (u_char *) p > pool->end) { 453 ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool"); 454 goto fail; 455 } 456 // 计算page偏移,并找到相应的ngx_slab_page_t结构(slab_page管理结构) 457 n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; 458 page = &pool->pages ; 459 slab = page->slab; 460 type = page->prev & NGX_SLAB_PAGE_MASK; 461 462 switch (type) { 463 464 case NGX_SLAB_SMALL: 465 /* 1、小块内存 * * 无法用uintptr_t slab;来标识一页内所有内存块的使用情况,因此, * 这里不用 page->slab 来标识该页内所有内存块的使用情况,而是 * 使用页数据空间的开始几个uintptr_t空间来表示了 * * 在 1、小块内存 中,page->slab存放等长内存块的大小(用位偏移的方式存储) * 因此,size即小块内存块的大小 */ 466 shift = slab & NGX_SLAB_SHIFT_MASK; 467 size = 1 << shift; 468 // 由于已经进行过内存对齐, 所以p的地址一定是slot(小块内存块)大小的整数倍,否则异常 469 if ((uintptr_t) p & (size - 1)) { 470 goto wrong_chunk; 471 } 472 /* * 这里很巧妙: * 由于前面对页进行了内存对齐的处理,因此下面的式子可直接 * * 求出p对应的slot块的位置,即p对应的小块内存位于page中的第几个块 */ 473 n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift; // 求出在uintptr_t中,p对应的偏移,即求出在uintptr_t中的第几位 474 m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1)); /* * 由于小块内存的bitmap是使用页数据空间的开始几个uintptr_t空间来表示的 * 所以求出该小块内存对应的uintptr_t的偏移,即求出第几个uintptr_t * * 至此,即(页数据空间的开始几个uintptr_t空间)第n个uintptr_t的第m位用来标识该小块内存的使用情况 */ 475 n /= (sizeof(uintptr_t) * 8); /* * 求出p对应的page页的位置,即真实内存的地址,主要因为是已经进行过了内存页对齐,所以这里可以这样直接计算出page首地址 * 这里因为小块内存的bitmap是使用页数据空间的开始几个uintptr_t空间来表示的 * 因此,page页的首地址即bitmap的首地址 */ 476 bitmap = (uintptr_t *) 477 ((uintptr_t) p & ~((uintptr_t) ngx_pagesize - 1)); 478 // 如果(bitmap)第n个uintptr_t的第m位确实为1 479 if (bitmap & m) { 480 // 如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中 481 if (page->next == NULL) { 482 slots = (ngx_slab_page_t *) 483 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 484 slot = shift - pool->min_shift; 485 486 page->next = slots[slot].next; 487 slots[slot].next = page; 488 489 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL; 490 page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL; 491 } 492 // 设置bitmap上对应位置为可用,即0 493 bitmap &= ~m; 494 /* * 下面的操作主要是查看这个页面是否都没用(空闲页), 如果是空闲页,则将页面链入free中 * * shift即page->slab存放等长内存块的大小(用位偏移的方式存储) * 计算位图存储了多少块 * 例如: * 假设小块内存大小为 32 bytes < 64 bytes, 因此shift = 5 * 4096 bytes 可以分为 128 个 32 bytes,因此只需要一个 32 bytes的小块内存作为 bitmap 即可 * 因此 n = (1 << (12 - 5)) / 8 / (1 << 5) = 0,修正n = 1,正如上所期望 */ 495 n = (1 << (ngx_pagesize_shift - shift)) / 8 / (1 << shift); 496 497 if (n == 0) { 498 n = 1; 499 } 500 501 if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) { 502 goto done; 503 } 504 // 计算位图使用了多少个uintptr_t 505 map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8); 506 // 查看其他uintptr_t是否都没使用 507 for (n = 1; n < map; n++) { 508 if (bitmap ) { 509 goto done; 510 } 511 } 512 // 如果释放该小块内存后,page变为空闲页,则进行进一步的回收 513 ngx_slab_free_pages(pool, page, 1); 514 515 goto done; 516 } 517 518 goto chunk_already_free; 519 520 case NGX_SLAB_EXACT: 521 /* * p所对应的slot块在slab(slot位图)中的位置. * (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift) 对应了第几个位 * 因此,m 直接对应了位图 */ 522 m = (uintptr_t) 1 << 523 (((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift); 524 size = ngx_slab_exact_size; 525 // 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍,否则异常 526 if ((uintptr_t) p & (size - 1)) { 527 goto wrong_chunk; 528 } 529 // 该 NGX_SLAB_EXACT 内存块对应的bitmap位为1 530 if (slab & m) { // 同上,如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中 531 if (slab == NGX_SLAB_BUSY) { 532 slots = (ngx_slab_page_t *) 533 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 534 slot = ngx_slab_exact_shift - pool->min_shift; 535 536 page->next = slots[slot].next; 537 slots[slot].next = page; 538 539 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT; 540 page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT; 541 } 542 543 page->slab &= ~m; 544 // 释放完内存块后,当前页状态为非满页,则跳转,避免后面的空闲页释放 545 if (page->slab) { 546 goto done; 547 } 548 549 ngx_slab_free_pages(pool, page, 1); 550 551 goto done; 552 } 553 554 goto chunk_already_free; 555 556 case NGX_SLAB_BIG: 557 /* * slab的高32位是slot块的位图,低32位用于存储slot块大小的偏移 * #define NGX_SLAB_SHIFT_MASK 0x0000000f * 64位系统上 * 64 bytes 2048 bytes * 3、大块内存(ngx_slab_exact_size < 内存大小 <= ngx_slab_max_size) 的情况 * page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数 * 这里用最低的一位十六进制表示就足够了,因为shift最大为11(表示内存块大小为2048 bytes * 因此,size为大块内存块的大小 */ 558 shift = slab & NGX_SLAB_SHIFT_MASK; 559 size = 1 << shift; 560 // 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍,否则异常 561 if ((uintptr_t) p & (size - 1)) { 562 goto wrong_chunk; 563 } 564 // 计算出该内存块对应的位图 m 565 m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift) 566 + NGX_SLAB_MAP_SHIFT); 567 568 if (slab & m) { 569 // 同上,如果页面的当前状态是全部已使用(全满页,全满页不在任何链表中;全满页释放一个内存块后变为半满页),则把它重新链入slot中 570 if (page->next == NULL) { 571 slots = (ngx_slab_page_t *) 572 ((u_char *) pool + sizeof(ngx_slab_pool_t)); 573 slot = shift - pool->min_shift; 574 575 page->next = slots[slot].next; 576 slots[slot].next = page; 577 578 page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG; 579 page->next->prev = (uintptr_t) page | NGX_SLAB_BIG; 580 } 581 582 page->slab &= ~m; 583 // #define NGX_SLAB_MAP_MASK 0xffffffff00000000 // 非空闲页,则跳过释放内存块后的空闲页释放 584 if (page->slab & NGX_SLAB_MAP_MASK) { 585 goto done; 586 } 587 588 ngx_slab_free_pages(pool, page, 1); 589 590 goto done; 591 } 592 593 goto chunk_already_free; 594 595 case NGX_SLAB_PAGE: 596 // 同上,由于已经进行过内存对齐, 所以p的地址一定是slot大小的整数倍(这里大块内存,所以地址跟内存页对其),否则异常 597 if ((uintptr_t) p & (ngx_pagesize - 1)) { 598 goto wrong_chunk; 599 } 600 601 if (slab == NGX_SLAB_PAGE_FREE) { 602 ngx_slab_error(pool, NGX_LOG_ALERT, 603 "ngx_slab_free(): page is already free"); 604 goto fail; 605 } 606 /* * 超大内存会使用1页或者多页,这些页在一起使用 * 对于这批页面中的第1页,slab的前3位会被设置为NGX_SLAB_PAGE_STATRT, 其余位表示紧随其后相邻的同批页面数 * 紧随其后相邻的同批页面的slab会被设置为NGX_SLAB_PAGE_BUSY */ 607 if (slab == NGX_SLAB_PAGE_BUSY) { 608 ngx_slab_error(pool, NGX_LOG_ALERT, 609 "ngx_slab_free(): pointer to wrong page"); 610 goto fail; 611 } 612 // 计算首地址在pool->start开始的第几个页,size表示同批页面中总共有几个相邻页,即需要归还的页面数 613 n = ((u_char *) p - pool->start) >> ngx_pagesize_shift; 614 size = slab & ~NGX_SLAB_PAGE_START; 615 // 归还size个页面 616 ngx_slab_free_pages(pool, &pool->pages , size); 617 618 ngx_slab_junk(p, size << ngx_pagesize_shift); 619 620 return; 621 } 622 623 /* not reached */ 624 625 return; 626 627 done: 628 629 ngx_slab_junk(p, size); 630 631 return; 632 633 wrong_chunk: 634 635 ngx_slab_error(pool, NGX_LOG_ALERT, 636 "ngx_slab_free(): pointer to wrong chunk"); 637 638 goto fail; 639 640 chunk_already_free: 641 642 ngx_slab_error(pool, NGX_LOG_ALERT, 643 "ngx_slab_free(): chunk is already free"); 644 645 fail: 646 647 return; 648 } /* * 在slab共享内存的管理结构ngx_slab_pool_t中有一个ngx_slab_page_t *pages成员用来存储所有页面的描述结构ngx_slab_page_t * 注意这里只是返回了对应页面的ngx_slab_page_t管理结构,并没有返回实际对应的真实内存地址 */ 651 static ngx_slab_page_t * 652 ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages) 653 { 654 ngx_slab_page_t *page, *p; 655 // 遍历free空闲页链表 656 for (page = pool->free.next; page != &pool->free; page = page->next) { 657 /* * 页管理结构ngx_slab_page_t,当页为空闲页时,其成员slab表示相邻的空闲页数 * * 这里表明有足够多的连续空闲页可供分配 */ 658 if (page->slab >= pages) { 659 // 如果链表中的这个页描述指明的连续页面数大于要求的pages,只取所需即可 // 将剩余的连续页面数仍然作为一个链表元素放在free池中 660 if (page->slab > pages) { 661 page[page->slab - 1].prev = (uintptr_t) &page[pages]; 662 663 page[pages].slab = page->slab - pages; 664 page[pages].next = page->next; 665 page[pages].prev = page->prev; 666 667 p = (ngx_slab_page_t *) page->prev; 668 p->next = &page[pages]; 669 page->next->prev = (uintptr_t) &page[pages]; 670 671 } else { // slab等于pages时,直接将pages页描述移出free链表即可 672 p = (ngx_slab_page_t *) page->prev; 673 p->next = page->next; 674 page->next->prev = page->prev; 675 } 676 /* * #define NGX_SLAB_PAGE_START 0x8000000000000000 * 这段连续页面的首页描述的slab里,高3位设置为NGX_SLAB_PAGE_START */ 677 page->slab = pages | NGX_SLAB_PAGE_START; 678 page->next = NULL; // prev定义页类型:存放size > ngx_slab_max_size的页级别内存块 679 page->prev = NGX_SLAB_PAGE; 680 // 如果只分配里1页,则直接返回 681 if (--pages == 0) { 682 return page; 683 } 684 // 如果分配了连续多个页面,则将后续的页描述也进行相应的初识化 685 for (p = page + 1; pages; pages--) { 686 p->slab = NGX_SLAB_PAGE_BUSY; 687 p->next = NULL; 688 p->prev = NGX_SLAB_PAGE; 689 p++; 690 } 691 692 return page; 693 } 694 } 695 696 if (pool->log_nomem) { 697 ngx_slab_error(pool, NGX_LOG_CRIT, 698 "ngx_slab_alloc() failed: no memory"); 699 } 700 701 return NULL; 702 } /* * 页面释放函数并不会将相邻的两个可用页面合并,仅仅将归还的页面链入free中, * 所以当用户请求的页面大于一页的时候要特别注意,尽量不要是使用slab_pool,否则很可能失败 */ 705 static void 706 ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page, 707 ngx_uint_t pages) 708 { 709 ngx_uint_t type; 710 ngx_slab_page_t *prev, *join; 711 // 计算第1页后跟的page的数目 712 page->slab = pages--; 713 // 如果是多页的情况,对跟的page的page管理结构slab_page_t进行清空。 714 if (pages) { 715 ngx_memzero(&page[1], pages * sizeof(ngx_slab_page_t)); 716 } 717 // 如果page后面还跟有节点,则将其连接至page的前一个结点 718 if (page->next) { 719 prev = (ngx_slab_page_t *) (page->prev & ~NGX_SLAB_PAGE_MASK); 720 prev->next = page->next; 721 page->next->prev = page->prev; 722 } 723 724 join = page + page->slab; 725 726 if (join < pool->last) { 727 type = join->prev & NGX_SLAB_PAGE_MASK; 728 729 if (type == NGX_SLAB_PAGE) { 730 731 if (join->next != NULL) { 732 pages += join->slab; 733 page->slab += join->slab; 734 735 prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 736 prev->next = join->next; 737 join->next->prev = join->prev; 738 739 join->slab = NGX_SLAB_PAGE_FREE; 740 join->next = NULL; 741 join->prev = NGX_SLAB_PAGE; 742 } 743 } 744 } 745 746 if (page > pool->pages) { 747 join = page - 1; 748 type = join->prev & NGX_SLAB_PAGE_MASK; 749 750 if (type == NGX_SLAB_PAGE) { 751 752 if (join->slab == NGX_SLAB_PAGE_FREE) { 753 join = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 754 } 755 756 if (join->next != NULL) { 757 pages += join->slab; 758 join->slab += page->slab; 759 760 prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 761 prev->next = join->next; 762 join->next->prev = join->prev; 763 764 page->slab = NGX_SLAB_PAGE_FREE; 765 page->next = NULL; 766 page->prev = NGX_SLAB_PAGE; 767 768 page = join; 769 } 770 } 771 } 772 773 if (pages) { 774 page[pages].prev = (uintptr_t) page; 775 } 776 // 将page重新归于slab_page_t的管理结构之下,放于管理结构的头部。 777 page->prev = (uintptr_t) &pool->free; 778 page->next = pool->free.next; 779 780 page->next->prev = (uintptr_t) page; 781 782 pool->free.next = page; 783 }
参考
Nginx内存管理及数据结构浅析–内存池
nginx 内存池分析
nginx slab内存管理
内存池到底为我们解决了什么问题
C++ 应用程序性能优化,第 6 章:内存池
相关文章推荐
- nginx进程间的通信机制源码分析(一)----共享内存
- 【Cocos2d-x 3.x】内存管理机制与源码分析
- cocos2dx内存管理机制学习笔记,源码分析
- Nginx源码分析(1)之——共享内存的配置、分配及初始化
- cocos2d-x源码分析::内存管理机制
- Android 匿名共享内存驱动源码分析
- .NET Framework 自动内存管理机制深入剖析 (C#分析篇)[转]
- nginx 源码学习笔记(七)——内存分配相关源码分析
- Memcached源码分析之内存管理
- redis源码分析(1)内存管理
- .NET Framework 自动内存管理机制深入剖析 (C#分析篇)
- Android 内存浅析【一】【管理、机制、分析】
- Memcached源码分析之内存管理篇之item结构图及slab结构图
- linux 内核源代码情景分析——i386 的页式内存管理机制
- .NET Framework 自动内存管理机制深入剖析 (C#分析篇) 读了三遍回味无穷
- PHP源码分析之内存管理
- memcached源码学习-内存管理机制slab allocator
- (转).NET Framework 自动内存管理机制深入剖析 (C#分析篇)
- Nginx源码分析-进程管理之master进程
- zz : memcached源码学习-内存管理机制slab allocator