Nginx源代碼分析-基本數據結構

本文分析基於Nginx-1.2.6,與舊版本或未來版本可能有些許出入,但應該差異不大,可作參考node

在Nginx中對array、list、queue、RB tree和hash表進行了實現,這些結構所涉及的內存管理都是在內存池中進行,源代碼都位於src/core目錄下。nginx

#Array# 相對來講,數組是Nginx中最簡單的數據結構,它是在內存池中分配的,與語言原生的數組相比,加強了功能,使用時可把它當作變長的,沒必要擔憂越界訪問。經過ngx_array_t結構體描述了數組的結構信息。算法

爲方便描述,稱ngx_array_t結構體爲數組的頭(header),爲數組元素分配的連續空間爲數組的體(body)。數組

其結構以下圖所示:數據結構

nginx數組結構

其中elts指向的是數組真實數據的存放位置,nelts是指數組中已經填充的元素個數,size是元素的大小,nalloc是分配的數組空間可容納的元素數,pool指向的是數組內存所在的內存池。函數

數組操做比較簡單,共有5個函數,ui

  • 建立ngx_array_create:其過程是首先分配空間給一個ngx_array_t結構體(即header),而後分配n*size大小的空間供數組中元素存放,以後初始化header,並返回它的首地址。google

  • 初始化ngx_array_init:它與建立相比,只少了第一個步驟。指針

  • 銷燬ngx_array_destroy:它並非真正地釋放數組所佔用空間(內存池中的內存釋放由內存池統一進行),而是有條件地更新內存池信息。(條件是:數組空間在內存池已用空間的最後位置)code

  • ngx_array_push:這也並非真正地往數組中添加一個元素,而只是向數組請求劃分出一個元素大小的空間並返回。此過程分兩種狀況, * 若是數組body中還有空閒,則把第一個可填充元素的位置返回; * 若是數組body已滿,而且

    • 數組body在內存池已用空間的最後位置,且緊挨數組body以後還有足夠的空間存放元素,則由內存池劃分緊挨body的一個元素空間給body。
    • 不然,在內存池中分配原數組body兩倍大小的空間,並把原數組中的元素複製到新空間,並更新數組的結構信息(header)。
  • ngx_array_push_n:同上面同樣,這也只是向數組請求劃分出n個元素大小的空間並返回,過程大同小異。

瞧下array的使用(這段代碼在ngx_hash.c中,在下面分析hash表時會見到ngx_hash_key_t 結構,可見ngx_array_push只是返回可填充元素的位置,具體內容還得再調用以後賦值:

<!-- lang: cpp -->
ngx_array_t   curr_names;
ngx_hash_key_t       *name;
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
                       sizeof(ngx_hash_key_t))
        != NGX_OK)
{
    return NGX_ERROR;
}
name = ngx_array_push(&curr_names);
if (name == NULL) {
    return NGX_ERROR;
}

name->key.len = len;
name->key.data = names[n].key.data;
name->key_hash = hinit->key(name->key.data, name->key.len);
name->value = names[n].value;

#List#

list結構

list結構中有個單向鏈表把全部的part串聯起來,並在last域中記錄下最後一個part便於在鏈表結尾插入新part,list中全部元素大小是相同的爲size,nalloc指的是在list中已經分配了多少個元素的空間。

list中只有三個相關操做,create,init,push,在ngx_list.h和ngx_list.c中定義。

與array相似的是,ngx_XXX_create只是對array或list的空間進行分配,固然它們的空間都分兩部分,一部分是header,一部分是body;ngx_XXX_push都只是返回一個能夠填充元素的位置並對header進行更新,而body中的元素內容在調用此函數以後再賦值填充。

與array不一樣的是,在push操做中,當已分配的空間都填滿元素時,list會新建一個part(ngx_list_part_t),並在新part中劃出空間並返回。

源碼中給出了遍歷list的示例代碼以下:

<!-- lang: cpp -->    
/*
 *  the iteration through the list:
 */
part = &list.part;
data = part->elts;

for (i = 0 ;; i++) {

    if (i >= part->nelts) {
       if (part->next == NULL) {
            break;
        }  
       part = part->next;
       data = part->elts;
       i = 0;
    }

    ...  data[i] ...
}

#queue#

queue是中雙向循環隊列,設有一標記(sentinel),它以後是隊列頭,以前是隊列尾。結構以下圖所示。

queue結構

對queue的操做多用宏定義,除圖中畫出的,還有:

  • ngx_queue_remove(x) :隊列中移除節點x
  • ngx_queue_split(h, q, n) :h爲隊列的標記,q爲隊列中的一個節點,這個宏意思是把原隊列拆分紅兩個,一個是仍以h爲標記的隊列,其中包含了原隊列q以前(到標記爲止)的全部結點,另外一個是一n爲標記的隊列,其中包含了原隊列q以後(包括q,到標記爲止)的全部節點。
  • ngx_queue_add(h, n) :h和n是兩個隊列的標記,這個宏意思是將n隊列中的節點(除了標記n)鏈接到h隊列的末尾,將兩個隊列進行合併,這樣組成了一個以h爲標記包含原來兩個隊列全部節點的新隊列。

ngx_queue_t中並無存儲數據,因此使用時需在自定義結構體中嵌入一個ngx_queue_t類型的變量,其使用方法以下圖所示:

使用方法

另外有兩個函數:

  • ngx_queue_middle(queue)有註釋:尋找隊列中間節點(find the middle queue element if the queue has odd number of elements or the first element of the queue's second part otherwise)
  • ngx_queue_sort(queue, cmp)使用穩定插入排序算法對queue隊列進行排序,完成後在next方向上爲升序

#RB Tree#

紅黑樹是一種平衡的二叉查找樹。其性質和操做在各類算法書上都有,搜一下,也不少,不細講。貼上相關結構代碼以下:

<!-- lang: cpp -->    
typedef ngx_uint_t  ngx_rbtree_key_t;
typedef ngx_int_t   ngx_rbtree_key_int_t;


typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
    ngx_rbtree_key_t       key;
    ngx_rbtree_node_t     *left;
    ngx_rbtree_node_t     *right;
    ngx_rbtree_node_t     *parent;
    u_char                 color;
    u_char                 data;
};


typedef struct ngx_rbtree_s  ngx_rbtree_t;

typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;
    ngx_rbtree_node_t     *sentinel;
    ngx_rbtree_insert_pt   insert;
};

其中*sentinel就是葉子節點(Nil)。

函數ngx_rbtree_insert_value和ngx_rbtree_insert_timer_value爲實現的ngx_rbtree_insert_pt handler,是按普通二叉查找樹的方式插入一個節點,並標記此節點的color爲紅色,這是紅黑樹中插入節點的第一個步驟。二者的區別,在於節點key值的比較,後者考慮了值溢出的狀況,是針對節點key是timer值而實現的。

<!-- lang: cpp -->  
/*在ngx_rbtree_insert_value中*/
 p = (node->key < temp->key) ? &temp->left : &temp->right;

/* 在ngx_rbtree_insert_timer_value中*/
/*    
* Timer values
* 1) are spread in small range, usually several minutes,
* 2) and overflow each 49 days, if milliseconds are stored in 32 bits.  
* The comparison takes into account that overflow.
*/
    
/*  
*   要比較的兩個timer值一般相差不大,大概幾分鐘而已
*   而0xFFFFFFFF小於50天的毫秒數,因此當值超過50天時就會溢出
*   因此0xFFFFFFF0和0x0000000F的timer值比較,應該認爲0x0000000F值溢出
*    其原本值應該大於0xFFFFFFF0,因此纔有此區別。
*/
p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)
        ? &temp->left : &temp->right;

//紅黑樹tree中插入節點node
void ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree,ngx_rbtree_node_t *node)

 //紅黑樹tree中刪除節點node
void ngx_rbtree_delete(ngx_thread_volatile ngx_rbtree_t *tree,    ngx_rbtree_node_t *node)

#hash# 這裏已經有很好的分析,建立hash和查找的過程可參考他那裏。

把他的圖也複製了過來,hash

下面就註釋下ngx_hash_init函數吧

<!-- lang: cpp -->
/* names數組中有nelts個ngx_hash_key_t ,是要填充到hash表中的數據*/
ngx_int_t
ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
{
u_char          *elts;
size_t           len;
u_short         *test;
ngx_uint_t       i, n, key, size, start, bucket_size;
ngx_hash_elt_t  *elt, **buckets;

/*bucket_size爲每一個桶容許佔用的最大空間,經過這個循環保證桶有足夠空間
  *  盛放至少一個ngx_hash_elt_t,另外ngx_hash_elt_t可看作變長結構,由於如圖所示
  * 緊挨其結構體以後仍然存放這name剩餘的len-1個字節
  * NGX_HASH_ELT_SIZE(&names[n])意思是
  *  與names[n]對應的的ngx_hash_elt_t所需的空間*/
for (n = 0; n < nelts; n++) {
    if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
    {
        ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
                      "could not build the %s, you should "
                      "increase %s_bucket_size: %i",
                      hinit->name, hinit->name, hinit->bucket_size);
        return NGX_ERROR;
    }
}
/*test中存放的是每一個桶所需的空間*/
test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
if (test == NULL) {
    return NGX_ERROR;
}

bucket_size = hinit->bucket_size - sizeof(void *);

start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;

if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
    start = hinit->max_size - 1000;
}
/*size爲桶個數,從start開始,當分配的桶個數合適時跳到found*/
for (size = start; size < hinit->max_size; size++) {

    ngx_memzero(test, size * sizeof(u_short));

    for (n = 0; n < nelts; n++) {
        if (names[n].key.data == NULL) {
            continue;
        }

        key = names[n].key_hash % size;
        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));

        if (test[key] > (u_short) bucket_size) {
            goto next;/*若是有個桶所需空間超過了容許最大值則增長桶的數量*/
        }
    }

    goto found;

next:

    continue;
}
/*桶數量已經最大,仍然有某個桶沒有足夠的空間盛放對應的項*/
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
              "could not build the %s, you should increase "
              "either %s_max_size: %i or %s_bucket_size: %i",
              hinit->name, hinit->name, hinit->max_size,
              hinit->name, hinit->bucket_size);

ngx_free(test);

return NGX_ERROR;

found:
/*此時size是合適的桶個數*/
for (i = 0; i < size; i++) {
    test[i] = sizeof(void *);
}
/*桶個數定下後,test中是每一個桶應分配的內存大小*/
for (n = 0; n < nelts; n++) {
    if (names[n].key.data == NULL) {
        continue;
    }

    key = names[n].key_hash % size;
    test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}

len = 0;
/*循環結束後len中存放的是爲全部的桶應分配的內存大小*/
for (i = 0; i < size; i++) {
    if (test[i] == sizeof(void *)) {
        continue;
    }
    /*每一個桶對齊到cacheline邊界上時所需的內存大小*/
    test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

    len += test[i];
}
/*這個地方圖上有解釋*/
if (hinit->hash == NULL) {
    hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                         + size * sizeof(ngx_hash_elt_t *));
    if (hinit->hash == NULL) {
        ngx_free(test);
        return NGX_ERROR;
    }

    buckets = (ngx_hash_elt_t **)
                  ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));

} else {
    buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
    if (buckets == NULL) {
        ngx_free(test);
        return NGX_ERROR;
    }
}
/*全部桶所佔空間的起始位置也要對齊到cacheline邊界上*/
elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
if (elts == NULL) {
    ngx_free(test);
    return NGX_ERROR;
}
elts = ngx_align_ptr(elts, ngx_cacheline_size);

/*將buckets中的每一個桶指針指向對應的桶空間首地址*/
for (i = 0; i < size; i++) {
    if (test[i] == sizeof(void *)) {
        continue;
    }

    buckets[i] = (ngx_hash_elt_t *) elts;
    elts += test[i];

}

for (i = 0; i < size; i++) {
    test[i] = 0;
}
/*填充每一個元素到對應的桶的對應位置*/
for (n = 0; n < nelts; n++) {
    if (names[n].key.data == NULL) {
        continue;
    }

    key = names[n].key_hash % size;
    elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

    elt->value = names[n].value;
    elt->len = (u_short) names[n].key.len;

    ngx_strlow(elt->name, names[n].key.data, names[n].key.len);

    test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}
/*每一個桶的結尾是一值爲NULL的指針*/
for (i = 0; i < size; i++) {
    if (buckets[i] == NULL) {
        continue;
    }

    elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);

    elt->value = NULL;
}

ngx_free(test);
/*把分配的buckets鏈接到hash上,並在hash->size中記錄下桶的個數*/
hinit->hash->buckets = buckets;
hinit->hash->size = size;

return NGX_OK;
}

ngx_hash_key和ngx_hash_key_lc是實現的兩個ngx_hash_key_pt,用來對字符串生成hash值,區別是後者計算的是字符串所有小寫後的hash值。

至於還有個ngx_hash_wildcard_init,裏面有遞歸調用,如今還迷糊着,先放着吧。。

相關文章
相關標籤/搜索