本来这一篇作为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的结构,如下图示:
nginx slab内存管理
上面需要注意的几个地方:
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

相关文章: