Refer:
《深入剖析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[n] != NGX_SLAB_BUSY) {
212
213 for (m = 1, i = 0; m; m <<= 1, i++) {
214 if ((bitmap[n] & m)) {
// 当前位表示的块已被使用了
215 continue;
216 }
217
// 找到了还没有被占用的内存块,设置bitmap位占位
218 bitmap[n] |= 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[n] == NGX_SLAB_BUSY) {
224 for (n = n + 1; n < map; n++) {
225 if (bitmap[n] != 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[n];
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[n] & 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[n] &= ~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[n]) {
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[n], 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 章:内存池