1. 程式人生 > >[nginx] 記憶體池與基本容器

[nginx] 記憶體池與基本容器

一、nginx記憶體池

  • 與STL的空間配置器類似,有兩級配置,僅提供了釋放大記憶體塊的介面,小記憶體只分配不釋放,要麼釋放整個記憶體池(nginx程序中有茫茫多記憶體池)
  • 記憶體池由三部分組成:小塊記憶體形成的連結串列(並不是ngx_list_t)、大塊記憶體形成的連結串列、以及掛載了一些記憶體池釋放同時釋放的資源(到底是啥,待補充)
  • 其中小塊記憶體連結串列每個格子大小相同,剩餘空間不足時再申請格子掛在該連結串列裡,這裡有個特別的點:每個當前格子有個失敗計數器,在給呼叫方分配記憶體時只有當計數器>4後再調整當前格子為下一個格子,目的是當前格子雖然不滿足分配需求,但也有剩餘,可能滿足下一次分配

ngx_pool_t

二、ngx_array_t

// 動態陣列
struct ngx_array_s {
    // elts指向陣列的首地址
    void        *elts; 
    // nelts是陣列中已經使用的元素個數
    ngx_uint_t   nelts; 
    // 每個陣列元素佔用的記憶體大小
    size_t       size;  
    // 當前陣列中能夠容納元素個數的總大小
    ngx_uint_t   nalloc; 
    // 記憶體池物件
    ngx_pool_t  *pool;  
};

ngx_array_push(ngx_array_t *a)

,如果陣列已滿,會判斷記憶體池pool是否剛為自己分配的記憶體,如果是則將記憶體池使用量(還有剩餘)指標向下移一個array元素,陣列元素也加1;如果不是,則分配2*陣列長度的記憶體,再memcpy到新陣列
ngx_array_t

三、ngx_list_t

nginx的連結串列容器,參考nginx原始碼分析—連結串列結構ngx_list_t,注意:連結串列中的元素是固定大小的陣列,但不是ngx_array_t物件

// ngx_list_part_s是代表ngx_list_t連結串列的一個節點。
// 它自身包含了一個數組,用來存放最終的元素
struct ngx_list_part_s {
    void
*elts; //連結串列元素elts陣列,陣列申請的空間大小為size*nalloc ngx_uint_t nelts; //當前已使用的elts個數,一定要小於等於nalloc ngx_list_part_t *next; //指向ngx_list_t中的下個連結串列part }; // ngx_list_t結構是一個連結串列,連結串列中每個節點是ngx_list_part_t結構。 // 而ngx_list_part_t中有個elts是一個數組,儲存了任意大小固定的元素,它是由ngx_pool_t分配的連續空間 typedef struct { ngx_list_part_t *last; //指向連結串列中最後一個元素,其作用相當於尾指標。插入新的節點時,從此開始。 ngx_list_part_t part; //連結串列中第一個元素,其作用相當於頭指標。遍歷時,從此開始。 size_t size; //連結串列中每個元素的大小 ngx_uint_t nalloc; //連結串列的每個ngx_list_part_t中elts陣列的所能容納的最大元素個數 ngx_pool_t *pool; //當前list資料存放的記憶體池 } ngx_list_t;

ngx_list_push(ngx_list_t *l)連結串列已滿時,從記憶體池pool中新申請一個節點,再申請陣列元素
ngx_list_t

四、ngx_queue_t

nginx的雙向佇列容器,參考nginx原始碼分析—佇列結構ngx_queue_t,不負責存放資料元素,需要使用方組織資料,幾乎所有操作都由巨集來完成

// 節點既佇列,佇列既節點
struct ngx_queue_s {
    ngx_queue_t  *prev;   //前一個
    ngx_queue_t  *next;   //下一個
};
// 以下為自己組織的程式碼
typedef struct {  
    int data;  
    ngx_queue_t queue;    //放在最後面
} my_queue_t;  
  • 支援頭/尾插入和遍歷
  • 支援佇列拆分和連結
  • 支援佇列排序,以及查詢中間
myque = ngx_palloc(pool, sizeof(ngx_queue_t));  //alloc a queue head  
ngx_queue_init(myque);  //init the queue  

//insert  some data into the queue  
for (i = 0; i < 100; i++)  
{  
    p = (my_queue_t*)ngx_palloc(pool, sizeof(my_queue_t));  
    p->data = i;
    ngx_queue_init(&p->queue);  

    //insert this data into the points queue  
    ngx_queue_insert_head(myque, &p->queue);  
}  

五、nginx_hash_t

nginx的hash容器,開鏈法,但每個桶裡裝的不是連結串列,而是陣列(不是ngx_array_t容器),這與容器的使用場景有關:初始化時載入所有資料,只有檢索操作,沒有增刪改

//hash元素結構
typedef struct {
    void             *value;    //value,即某個key對應的值,即<key,value>中的value
    u_short           len;      //name長度
    u_char            name[1];  //<key,value>中的key,字串直接放在&name[0]中
} ngx_hash_elt_t;
//hash結構
typedef struct {
    ngx_hash_elt_t  **buckets; //hash桶(有size個桶)
    ngx_uint_t        size;    //hash桶個數
} ngx_hash_t;

ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts),hash容器初始化函式,其中ngx_hash_init_t封裝了要初始化的hash物件、桶的最大數目,每個桶內陣列的最大長度bucket_size。初始化的names為所有(key, value),建立容器時會將桶數量從小到大遍歷,直到每個桶裡陣列記憶體<最大長度bucket_size,再將每個元素放入各個桶中。注意,這裡的桶內陣列沒有長度,以ngx_hash_elt_t.value=NULL為結束條件
ngx_hash_t

六、ngx_hash_wildcard_t

nginx處理字串萬用字元匹配的hash表,重要的應用場景是虛擬主機server name 的匹配,參考nginx原始碼分析之hash的實現,能處理如下模式:

  • *.abc.com,匹配www.abc.com、qqq.www.abc.com之類,但不能匹配abc.com
  • .abc.com,匹配abc.com、www.abc.com、qqq.www.abc.com之類
  • www.abc.*,匹配www.abc.com、www.abc.com.cn
// 萬用字元匹配hash
typedef struct {
    // 基本散列表
    ngx_hash_t        hash;
    // 當使用這個ngx_hash_wildcard_t萬用字元散列表作為某容器的元素時,可以使用這個value指標指向使用者資料
    void             *value;
} ngx_hash_wildcard_t;

// 字串匹配hash集合,應用時使用該結構體
typedef struct {
    // 用於精確匹配的基本散列表
    ngx_hash_t            hash;
    // 用於查詢前置萬用字元的散列表
    ngx_hash_wildcard_t  *wc_head;
    // 用於查詢後置萬用字元的散列表
    ngx_hash_wildcard_t  *wc_tail;
} ngx_hash_combined_t;
  • ngx_hash_keys_arrays_t,收集所有的字串對,會按嚴格匹配串、字首串、字尾串存放,其中字首串會反向,然後前/字尾都去掉末尾最後一個字元。在收集字串時,為了判重,在該結構體內分別掛了對應簡易hash(桶為ngx_array_t),支援add
  • 嚴格匹配串、字首串、字尾串分別初始化成ngx_hash_t放到ngx_hash_combined_t注意:其中字首串、字尾串在初始化前需要排序
  • ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts),初始化萬用字元hash,會將字串按.分割成多層hash,比如:
    • {key = (“com.” , 4 ), key_hash = 0 , value = “220.181.111.147”}
    • {key = (“cn.com.baidu.” , 13), key_hash = 0 , value = “220.181.111.147”}
    • {key = (“com.baidu.” , 10), key_hash = 0 , value = “220.181.111.147”}
    • {key = (“com.google.” , 11), key_hash = 0 , value = “58.63.236.35”}
  • 為了標記(key, value)中value是指定向實際的value,還是指向下一級的hash地址,程式碼中實現得很巧妙。由於nginx記憶體是4位元組對齊,故記憶體地址能整除4,即低兩位都為0,故value指標的低兩位可用來標記。其中01表示後面無wildcard雜湊,10表示指向最後一級wildcard雜湊,11表示後面還有很多級wildcard雜湊

wildcard

ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len),檢索hash表,順序:嚴格匹配表->字首表->字尾表,所以在(*.baidu.com,1)、(map.baidu.com,2)的集合中,map.baidu.com會匹配到2

  • ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len),嚴格匹配函式,桶對映,然後遍歷桶內陣列
  • ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len),字首匹配函式,將目標串分割,根據value的標記,遞迴呼叫
  • ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len),字尾匹配函式,將目標串分割,根據value的標記,遞迴呼叫