1 nginx的基數樹簡介
基數樹是一種二叉查找樹,它具備二叉查找樹的所有優點:檢索、插入、刪除節點速度快,支持範圍查找,支持遍歷等。在nginx中僅geo模塊使用了基數樹。nginx的基數樹使用ngx_radix_tree_t這個結構體表示的。ngx_radix_tree_t要求存儲的每個節點都必須以32位整形作爲區別任意兩個節點的唯一標識。ngx_radix_tree_t基數樹會負責分配每個節點佔用的內存,基數樹的每個節點中可存儲的值只是一個指針,這個指針指向實際的數據。
節點結構ngx_radix_node_t:
typedef struct ngx_radix_node_s ngx_radix_node_t;
//基數樹的節點
struct ngx_radix_node_s {
ngx_radix_node_t *right;//右子指針
ngx_radix_node_t *left;//左子指針
ngx_radix_node_t *parent;//父節點指針
uintptr_t value;//指向存儲數據的指針
};
基數樹ngx_radix_tree_t:
typedef struct {
ngx_radix_node_t *root;//根節點
ngx_pool_t *pool;//內存池,負責分配內存
ngx_radix_node_t *free;//回收釋放的節點,在添加新節點時,會首先查看free中是否有空閒可用的節點
char *start;//已分配內存中還未使用內存的首地址
size_t size;//已分配內存內中還未使用內存的大小
} ngx_radix_tree_t;
這裏要注意free這個成員,它用來回收刪除基數樹上的節點,並這些節點連接成一個空閒節點鏈表,當要插入新節點時,首先查看這個鏈表是否有空閒節點,如果有就不申請節點空間,就從上面取下一個節點。
2ngingx基數的基本操作函數
ngx_radix_tree_t基本操作函數如下:
//創建基數樹,preallocate是預分配節點的個數
ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate);
//根據key值和掩碼向基數樹中插入value,返回值可能是NGX_OK,NGX_ERROR, NGX_BUSY
ngx_int_t ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask, uintptr_t value);
//根據key值和掩碼刪除節點(value的值)
ngx_int_t ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask);
//根據key值在基數樹中查找返回value數據
uintptr_t ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key);
2.1 ngx_radix_tree_create創建基數樹
ngx_radix_tree_create會構造一個基數樹。這個函數會根據第二個參數來判斷是否預先創建一棵空的基數樹:
1)如果preallocate爲0,只申請ngx_radix_tree_t這個結構體,並返回
2)如果preallocate爲-1,會根據ngx_pagesize/sizeof(ngx_radix_tree_t)的情況來構造一棵深度爲7(或者8)的沒有存儲數據的基數樹。
源代碼:
ngx_radix_tree_t *
ngx_radix_tree_create(ngx_pool_t *pool, ngx_int_t preallocate)
{
uint32_t key, mask, inc;
ngx_radix_tree_t *tree;
tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));//申請ngx_raidx_tree_t,這個tree是返回的指針
if (tree == NULL) {
return NULL;
}
//初始化ngx_radix_tree_t本身
tree->pool = pool;
tree->free = NULL;
tree->start = NULL;
tree->size = 0;
tree->root = ngx_radix_alloc(tree);//申請一個基數節點
if (tree->root == NULL) {
return NULL;
}
//初始化root節點
tree->root->right = NULL;
tree->root->left = NULL;
tree->root->parent = NULL;
tree->root->value = NGX_RADIX_NO_VALUE;
/*prealloc=0時,只創建結構體ngx_radix_tree_t,沒有創建任何基數樹節點*/
if (preallocate == 0) {
return tree;
}
/*prealloc=-1時,根據下面的情況創建基數樹節點*/
if (preallocate == -1) {
switch (ngx_pagesize / sizeof(ngx_radix_tree_t)) {
/* amd64 */
case 128:
preallocate = 6;
break;
/* i386, sparc64 */
case 256:
preallocate = 7;
break;
/* sparc64 in 32-bit mode */
default:
preallocate = 8;
}
}
mask = 0;
inc = 0x80000000;
//加入preallocate=7,最終建的基數樹的節點總個數爲2^(preallocate+1)-1,每一層個數爲2^(7-preallocate)
//循環如下:
//preallocate = 7 6 5 4 3 2 1
//mask(最左8位)= 10000000 11000000 11100000 11110000 11111000 11111100 11111110
//inc = 10000000 01000000 00100000 00010000 00001000 00000100 00000010
//增加節點個數 = 2 4 8 16 32 64 128
while (preallocate--) {
key = 0;
mask >>= 1;
mask |= 0x80000000;
do {//根據inc的值添加節點
if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
!= NGX_OK)
{
return NULL;
}
key += inc;//當preallocate=0時,是最後一層,構建的節點個數爲2^preallocate
} while (key);
inc >>= 1;
}
return tree;
}
2.2 ngx_radix32tree_insert向基數樹中插入樹節點
nginx的基數樹只處理key值爲整形的情況,所以每個整形被轉化爲二進制數,並且樹的最大深度是32層。根據二進制位數從左到右,如果當前位爲1,就向右子樹,否則向左子樹插入。當然有時候我們不想構建深度爲32的基數樹,nginx爲此提供了一個掩碼mask,這個掩碼中1的個數決定了基數樹的深度。
代碼:
ngx_int_t
ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask,
uintptr_t value)
{
uint32_t bit;
ngx_radix_node_t *node, *next;
bit = 0x80000000;//從最左位開始,判斷key值
node = tree->root;
next = tree->root;
while (bit & mask) {
if (key & bit) {//等於1向右查找
next = node->right;
} else {//等於0向左查找
next = node->left;
}
if (next == NULL) {
break;
}
bit >>= 1;
node = next;
}
if (next) {//如果next不爲空
if (node->value != NGX_RADIX_NO_VALUE) {//如果數據不爲空
return NGX_BUSY;//返回NGX_BUSY
}
node->value = value;//直接賦值
return NGX_OK;
}
//如果next爲中間節點,且爲空,繼續查找且申請路徑上爲空的節點
//比如找key=1000111,在找到10001時next爲空,那要就要申請三個節點分別存10001,100011,1000111,
//1000111最後一個節點爲key要插入的節點
while (bit & mask) {//沒有到達最深層,繼續向下申請節點
next = ngx_radix_alloc(tree);//申請一個節點
if (next == NULL) {
return NGX_ERROR;
}
//初始化節點
next->right = NULL;
next->left = NULL;
next->parent = node;
next->value = NGX_RADIX_NO_VALUE;
if (key & bit) {
node->right = next;
} else {
node->left = next;
}
bit >>= 1;
node = next;
}
node->value = value;//指向數據區
return NGX_OK;
}
2.3ngx_radix32tree_delete刪除節點
刪除一個節點和插入節點的操作幾乎一樣,不過要注意兩點:
1)如果刪除的是葉子節點,直接從基數樹中刪除,並把這個節點放入free鏈表
2)如果不是葉子節點,把value值置爲NGX_RADIX_NO_VALUE
代碼:
ngx_int_t
ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
{
uint32_t bit;
ngx_radix_node_t *node;
bit = 0x80000000;
node = tree->root;
//根據key和掩碼查找
while (node && (bit & mask)) {
if (key & bit) {
node = node->right;
} else {
node = node->left;
}
bit >>= 1;
}
if (node == NULL) {//沒有找到
return NGX_ERROR;
}
//node不爲葉節點直接把value置爲空
if (node->right || node->left) {
if (node->value != NGX_RADIX_NO_VALUE) {//value不爲空
node->value = NGX_RADIX_NO_VALUE;//置空value
return NGX_OK;
}
return NGX_ERROR;//value爲空,返回error
}
//node爲葉子節點,直接放到free區域
for ( ;; ) {//刪除葉子節點
if (node->parent->right == node) {
node->parent->right = NULL;//
} else {
node->parent->left = NULL;
}
//把node鏈入free鏈表
node->right = tree->free;//放到free區域
tree->free = node;//free指向node
//假如刪除node以後,父節點是葉子節點,就繼續刪除父節點,
//一直到node不是葉子節點
node = node->parent;
if (node->right || node->left) {//node不爲葉子節點
break;
}
if (node->value != NGX_RADIX_NO_VALUE) {//node的value不爲空
break;
}
if (node->parent == NULL) {//node的parent爲空
break;
}
}
return NGX_OK;
}
2.4ngx_radix32tree_find查找節點返回數據
這個函數是這四個函數中最簡單的一個,就是根據key值查詢,如果找到返回value值,沒有找到返回NGX_RADIX_NO_VALUE。
代碼:
uintptr_t
ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
{
uint32_t bit;
uintptr_t value;
ngx_radix_node_t *node;
bit = 0x80000000;
value = NGX_RADIX_NO_VALUE;
node = tree->root;
while (node) {
if (node->value != NGX_RADIX_NO_VALUE) {
value = node->value;
}
if (key & bit) {
node = node->right;
} else {
node = node->left;
}
bit >>= 1;//往下層查找
}
return value;
}
2.5ngx_radix_alloc申請節點函數
ngx_radix_alloc爲基數樹申請節點:
1)如果free鏈表不爲空,直接從上面取下一個空閒節點
2)free鏈表爲空,則申請一個節點
代碼:
static void *
ngx_radix_alloc(ngx_radix_tree_t *tree)
{
char *p;
if (tree->free) {//如果free中有可利用的空間節點
p = (char *) tree->free;//指向第一個可利用的空間節點
tree->free = tree->free->right;//修改free
return p;
}
if (tree->size < sizeof(ngx_radix_node_t)) {//如果空閒內存大小不夠分配一個節點就申請一頁大小的內存
tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
if (tree->start == NULL) {
return NULL;
}
tree->size = ngx_pagesize;//修改空閒內存大小
}
//分配一個節點的空間
p = tree->start;
tree->start += sizeof(ngx_radix_node_t);
tree->size -= sizeof(ngx_radix_node_t);
return p;
}
3測試例子下面是一個測試ngx_radix_tree_t的例子,在寫完這個測試列子運行的時候,出現“核心錯誤(存儲已轉移)”,先按老辦法調試--直接打印定位錯誤代碼範圍,找過了錯誤是在這個函數裏面:ngx_radix32tree_create,尼碼,源代碼有錯誤,蒙了,不知到怎麼調試下去,因爲以前沒在linux跟蹤代碼執行,沒法了,直接搜資料學gdb調試程序,單步跟蹤調試了整整一個晚上,發現在下面這句代碼中出錯:
tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
gdb p ngx_pagesize 竟然是0,暈了很久找到這個ngx_pagesize的定義,是個全局變量沒有初始化。
誰能知道系統中的全局變量在哪裏用,在哪裏初始化?
http://blog.csdn.net/xiaoliangsky/article/details/39695591
測試代碼:
/********************************************************
author: smtl
date: 2014-10-01--4:50
*********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <ngx_core.h>
#include <ngx_config.h>
#include <ngx_conf_file.h>
#include <ngx_palloc.h>
#include <ngx_radix_tree.h>
////////////////////////////////////////////////////////////////////////////////////
//不加下面這兩個定義編譯會出錯
volatile ngx_cycle_t *ngx_cycle;
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const char *fmt, ...)
{
}
////////////////////////////////////////////////////////////////////////////////////
//先序遍歷radix_tree
void travel_radix_tree(ngx_radix_node_t *root);
int main()
{
/*基數樹節點的數據集:ngx_int_t類型,只是測試,實際上可以爲任何類型*/
ngx_int_t data[64];
ngx_int_t i = 0;
for (i = 0; i < 64; ++i)
{
data[i] = rand()%10000;
}
/*創建內存池對象*/
ngx_pool_t* pool = ngx_create_pool(1024, NULL);
if (pool == NULL)
{
printf("create pool failed!\n");
exit(1);
}
//printf("xxxxx\n");
////////////////////////////////////////////////////////////////////////////////
//一定要初始化ngx_pagesize這個全局變量,調試了一個晚上
//不初始化,會出現段錯誤(核心已轉儲),這也是全局變量的潛在危害:
//你不知道你的程序中是否用到這個全局變量,如果用到,這個全局變量初始化了沒有
//在一些大的程序中,你根本無法快速知道這些,所以應儘量避免使用全局變量
ngx_pagesize = getpagesize();
printf("pagesize = %d\n", ngx_pagesize);
////////////////////////////////////////////////////////////////////////////////
/*創建基數樹,prealloc=0時,只創建結構體ngx_radix_tree_t,沒有創建任何基數樹節點*/
ngx_radix_tree_t *tree = ngx_radix_tree_create(pool, -1);
//printf("xxxxxY\n");
if (tree == NULL)
{
printf("crate radix tree failed!\n");
exit(1);
}
/*插入data*/
ngx_uint_t deep = 5;//樹的最大深度爲4
ngx_uint_t mask = 0;
ngx_uint_t inc = 0x80000000;
ngx_uint_t key = 0;
ngx_uint_t cunt = 0;//data數組的索引
while (deep--)
{
key = 0;
mask >>= 1;
mask |= 0x80000000;
do
{
if (NGX_OK != ngx_radix32tree_insert(tree, key, mask, &data[cunt]))
{
printf("insert error\n");
exit(1);
}
key += inc;
++cunt;
if (cunt > 63)
{
cunt = 63;
}
}while(key);
inc >>= 1;
}
/*先序打印數據*/
travel_radix_tree(tree->root);
printf("\n");
/*查找測試*/
ngx_uint_t tkey = 0x58000000;
ngx_int_t* value = ngx_radix32tree_find(tree, 0x58000000);
if (value != NGX_RADIX_NO_VALUE)
{
printf("find the value: %d with the key = %x\n", *value, tkey);
}
else
{
printf("not find the the value with the key = %x\n", tkey);
}
/*刪除測試*/
if (NGX_OK == ngx_radix32tree_delete(tree, tkey, mask))
{
printf("delete the the value with the key = %x is succeed\n", tkey);
}
else
{
printf("delete the the value with the key = %x is failed\n", tkey);
}
value = ngx_radix32tree_find(tree, 0x58000000);
if (value != NGX_RADIX_NO_VALUE)
{
printf("find the value: %d with the key = %x\n", *value, tkey);
}
else
{
printf("not find the the value with the key = %x\n", tkey);
}
return 0;
}
void travel_radix_tree(ngx_radix_node_t *root)
{
if (root->left != NULL)
{
travel_radix_tree(root->left);
}
if (root->value != NGX_RADIX_NO_VALUE)
{
ngx_int_t* value = root->value;
printf("%d\n", *value);
}
if (root->right != NULL)
{
travel_radix_tree(root->right);
}
}
work hard!