本文分析基於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)。數組
其結構以下圖所示:數據結構
其中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已滿,而且
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結構中有個單向鏈表把全部的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的操做多用宏定義,除圖中畫出的,還有:
ngx_queue_t中並無存儲數據,因此使用時需在自定義結構體中嵌入一個ngx_queue_t類型的變量,其使用方法以下圖所示:
另外有兩個函數:
#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和查找的過程可參考他那裏。
把他的圖也複製了過來,
下面就註釋下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,裏面有遞歸調用,如今還迷糊着,先放着吧。。