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

然后补充下,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 }
View Code

下面是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 }
View Code

补充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 }
View Code

然后再看下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的组合。导致申请大的内存失败。

 转载请注明出处:http://www.cnblogs.com/doop-ymc/p/3412572.html

posted on 2013-11-07 15:29  doop-ymc  阅读(5002)  评论(0编辑  收藏