Nginx的slab page內存緩存機制

Nginx的內存緩存是通過slab pool來實現的,但是目前Nginx代碼沒有對http響應進行內存緩存。比如作爲反向代理服務器時向後端獲取的文件也只是緩存在磁盤裏,而內存只是用來做索引。不過Nginx已經提供了內存緩存功能的函數,所以如果在其他地方有需要使用內存緩存的話,也可以通過修改代碼來實現(當然,也可以用memory disk來實現內存緩存)。在Nginx的內存緩存機制中,最重要的結構就是ngx_slab_pool_t,裏面存放了包括內存緩存的空間使用情況、位置映射以及緩存空間本身的幾乎所有信息。先來看一下ngx_slab_pool_t吧。

C代碼 
  1. typedefstruct {  
  2.     ngx_atomic_t        lock;   //mutex的鎖
  3. size_t          min_size;   //內存緩存obj最小的大小,一般是1個byte
  4. size_t          min_shift;  //slab pool以shift來比較和計算所需分配的obj大小、
  5. //每個緩存頁能夠容納obj個數以及所分配的頁在緩存空間的位置
  6.     ngx_slab_page_t *pages;     //slab page空間的開頭
  7.     ngx_slab_page_t free;       //如果要分配新的頁,就從free.next開始
  8.     u_char          *start;     //實際緩存obj的空間的開頭
  9.     u_char          *end;       //整個緩存空間的結尾
  10.     ngx_shmtx_t     mutex;      //互斥鎖
  11.     u_char          *log_ctx;  
  12.     u_char          zero;  
  13. void            *data;        
  14. void            *addr;      //指向ngx_slab_pool_t的開頭
  15. } ngx_slab_pool_t;  
C代碼 
  1. struct ngx_slab_page_s {  
  2. uintptr_t       slab;   //多種情況,多個用途
  3. //當需要分配新的頁的時候,slab表示剩餘頁的數量
  4. //當分配某些大小的obj的時候(一個緩存頁存放多個obj),slab表
  5. //示被分配的緩存的佔用情況(是否空閒),以bit位來表示
  6.     ngx_slab_page_t *next;  //在分配較小obj的時候,next指向slab page在pool->pages的位置
  7. uintptr_t       prev;  
  8. };  

注意,在ngx_slab_pool_t裏面有兩種類型的slab page,雖然都是ngx_slab_page_t定義的結構,但是功能不盡相同。一種是slots,用來表示存放較小obj的內存塊(如果頁大小是4096B,則是<2048B的obj,即小於1/2頁),另一種來表示所要分配的空間在緩存區的位置。Nginx把緩存obj分成大的(>=2048B)和小的(<2048B)。每次給大的obj分配一整個頁,而把多個小obj存放在一個頁中間,用bitmap等方法來表示其佔用情況。而小的obj又分爲3種:小於128B,等於128B,大於128B且小於2048B。其中小於128B的obj需要在實際緩衝區額外分配bitmap空間來表示內存使用情況(因爲slab成員只有4個byte即32bit,一個緩存頁4KB可以存放的obj超過32個,所以不能用slab來表示),這樣會造成一定的空間損失。等於或大於128B的obj因爲可以用一個32bit的整形來表示其狀態,所以就可以直接用slab成員。每次分配的空間是2^n,最小是8byte,8,16,32,64,128,256,512,1024,2048。小於2^i且大於2^(i-1)的obj會被分配一個2^i的空間,比如56byte的obj就會分配一個64byte的空間。

先看一下初始化slab pool的函數,在ngx_init_cycle()中調用的ngx_init_zone_pool()中被調用

C代碼 
  1. void ngx_slab_init(ngx_slab_pool_t *pool)  
  2. {  
  3. //假設每個page是4KB
  4. //設置ngx_slab_max_size = 2048B。如果一個頁要存放多個obj,則obj size要小於這個數值
  5. //設置ngx_slab_exact_size = 128B。分界是否要在緩存區分配額外空間給bitmap
  6. //ngx_slab_exact_shift = 7,即128的位表示
  7. //...
  8. //pool->min_shift = 3
  9. //最小分配的空間是8byte
  10.     pool->min_size = 1 << pool->min_shift;  
  11. //這些slab page是給大小爲8,16,32,64,128,256,512,1024,2048byte的內存塊
  12. //這些slab page的位置是在pool->pages的前面
  13. //初始化
  14.     p = (u_char *) pool + sizeof(ngx_slab_pool_t);  
  15.     slots = (ngx_slab_page_t *) p;  
  16.     n = ngx_pagesize_shift - pool->min_shift;  
  17. for (i = 0; i < n; i++) {  
  18.         slots[i].slab = 0;  
  19.         slots[i].next = &slots[i];  
  20.         slots[i].prev = 0;  
  21.     }  
  22. //跳過上面那些slab page
  23.     p += n * sizeof(ngx_slab_page_t);  
  24. //**計算這個空間總共可以分配的緩存頁(4KB)的數量,每個頁的overhead是一個slab page的大小
  25. //**這兒的overhead還不包括之後給<128B物體分配的bitmap的損耗
  26.     pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));  
  27. //把每個緩存頁對應的slab page歸0
  28.     ngx_memzero(p, pages * sizeof(ngx_slab_page_t));  
  29. //pool->pages指向slab page的頭
  30.     pool->pages = (ngx_slab_page_t *) p;  
  31. //初始化free,free.next是下次分配頁時候的入口
  32.     pool->free.prev = 0;  
  33.     pool->free.next = (ngx_slab_page_t *) p;  
  34. //更新第一個slab page的狀態,這兒slab成員記錄了整個緩存區的頁數目
  35.     pool->pages->slab = pages;  
  36.     pool->pages->next = &pool->free;  
  37.     pool->pages->prev = (uintptr_t) &pool->free;  
  38. //實際緩存區(頁)的開頭,對齊
  39.     pool->start = (u_char *)ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t), ngx_pagesize);  
  40. //根據實際緩存區的開始和結尾再次更新內存頁的數目
  41.     m = pages - (pool->end - pool->start) / ngx_pagesize;  
  42. if (m > 0) {  
  43.         pages -= m;  
  44.         pool->pages->slab = pages;  
  45.     }  
  46. //...
  47. }  

下面來看一下需要分配緩存空間時調用的函數,由於是共享內存,所以在進程間需要用鎖來保持同步

C代碼 
  1. void * ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)  
  2. {  
  3. //spinlock獲取鎖
  4.     ngx_shmtx_lock(&pool->mutex);  
  5.     p = ngx_slab_alloc_locked(pool, size);  
  6. //解鎖
  7.     ngx_shmtx_unlock(&pool->mutex);  
  8. return p;  
  9. }  
C代碼 
  1. //返回的值是所要分配的空間在內存緩存區的位置
  2. void * ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)  
  3. {  
  4. //這兒假設page_size是4KB
  5. //如果是large obj, size >= 2048B
  6. if(...){  
  7. //分配1個或多個內存頁
  8.         page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1) >> ngx_pagesize_shift);  
  9. //返回指向內存緩存頁的位置,這兒slab page的位置與所要返回的緩存頁的位置是對應的
  10.         p = (page - pool->pages) << ngx_pagesize_shift;  
  11.         p += (uintptr_t) pool->start;  
  12. //done, return p
  13. //...
  14.     }  
  15. //較小的obj, size < 2048B
  16. //根據需要分配的size來確定在slots的位置,每個slot存放一種大小的obj的集合,如slots[0]表示8byte的空間,slots[3]表示64byte的空間
  17. //如果obj過小(<1B),slot的位置是1B空間的位置,即最小分配1B
  18. //...
  19. //如果之前已經有此類大小obj且那個已經分配的內存緩存頁還未滿
  20. if(...){  
  21. //小obj,size < 128B,更新內存緩存頁中的bitmap,並返回待分配的空間在緩存的位置
  22. //...
  23. //size == 128B,因爲一個頁可以放32個,用slab page的slab成員來標註每塊內存的佔用情況,不需要另外在內存緩存區分配bitmap,並返回待分配的空間在緩存的位置
  24. //...
  25. //size > 128B,也是更新slab page的slab成員,但是需要預先設置slab的部分bit,因爲一個頁的obj數量小於32個,並返回待分配的空間在緩存的位置
  26. //...
  27.     }  
  28. //此前沒有此類大小的obj或者之前的頁已經滿了,分配一個新的頁,page是新的頁相應的slab page
  29.     page = ngx_slab_alloc_pages(pool, 1);  
  30. //小obj,size < 128B,更新內存緩存頁中的bitmap,並返回待分配的空間在緩存的位置(跳過bitmap的位置)
  31. //...
  32. //size == 128B,更新slab page的slab成員(即頁中的每個相同大小空間的佔用情況),並返回待分配的空間在緩存的位置
  33. //...
  34. //size > 128B,更新slab page的slab成員(即頁中的每個相同大小空間的佔用情況),並返回待分配的空間在緩存的位置
  35. //...
  36. }  
C代碼 
  1. //返回一個slab page,這個slab page之後會被用來確定所需分配的空間在內存緩存的位置
  2. static ngx_slab_page_t * ngx_slab_alloc_pages(...)  
  3. {  
  4. //從pool->free.next開始,每次取(slab page) page = page->next
  5. for(;;){  
  6. //本個slab page剩下的緩存頁數目>=需要分配的緩存頁數目N
  7. if(...){  
  8. //更新從本個slab page開始往下第N個slab page的緩存頁數目爲本個slab page數目減去N
  9. //N爲需要分配的緩存頁數目
  10. //更新pool->free.next,下次從第N個slab page開始
  11. //...
  12. //更新被分配的page slab中的第一個的slab成員,即頁的個數和佔用情況
  13.             page->slab = pages | NGX_SLAB_PAGE_START;  
  14. //...
  15. //如果分配的頁數N>1,更新後面page slab的slab成員爲NGX_SLAB_PAGE_BUSY
  16. //...
  17. return page;  
  18.         }  
  19.     }  
  20. //沒有找到空餘的頁
  21. return NULL;  
  22. }  

附圖

1. ngx_slab_alloc_pages圖例:


2. 小物體bitmap圖例:



3. slab page和緩存頁的映射:






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章