nginx slab內存管理

原本這一篇做爲nginx系列的開頭是不合適的,不過因爲nginx進程框架本身的梳理還沒
完成,這部分又恰好整理完了,就從這開始吧。
這兒談的是nginx的slab的內存管理方式,這種方式的內存管理在nginx中,主要是與nginx
的共享內存協同使用的。nginx的slab管理與linux的slab管理相同的地方在於均是利用了內存
的緩存與對齊機制,slab內存管理中一些設計至關巧妙的地方,也有一些地方我的感受設計
不是很完美,或許是做爲nginx設計綜合考慮的結果。
nginx slab實現中的一大特點就是大量的位操做,這部分操做不少是與slot分級數組相關的。html

爲方便描述下面作一些說明:
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.linux

整個slab pool中有兩個很是重要的結構,一是ngx_slab_pool_t,即slab header,以下示:nginx

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;ide

先看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:返回獲得空間。oop

下面看具體的代碼:

  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

相關文章
相關標籤/搜索