Chinaunix首页 | 论坛 | 博客
  • 博客访问: 763626
  • 博文数量: 116
  • 博客积分: 923
  • 博客等级: 准尉
  • 技术积分: 1635
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-06 21:43
个人简介

一直帮老板搬运代码!!!

文章分类
文章存档

2013年(47)

2012年(69)

分类: LINUX

2012-10-07 20:08:53

ngx_radix_tree_t 是nginx里面的基树很少使用

它的学习点是:
typedef struct {
    ngx_radix_node_t  *root; //表示当前树的根结点,是使用中的结点。
    ngx_pool_t        *pool;
    ngx_radix_node_t  *free; //则是表示当前树的空闲结点,当一个树结点被 删除时,nginx 并没有将内存归还系统或pool,而是加入自己的free链表中,以供下次使用。
    char              *start;//是从内存池中申请的空闲内存,当free链表中的结点用完时,则从start指向的内存块中取一块,然后向后移动start指针,将修改size的值。如果start指向的内存不也不足了,则向内存池pool申请一块内存。
    size_t             size;
} ngx_radix_tree_t;

其他内部实现:

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include
#include


static void *ngx_radix_alloc(ngx_radix_tree_t *tree);

//创建基树
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;

    //    申请root节点
    tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));//申请树结构内存
    if (tree == NULL) {
        return NULL;
    }

    tree->pool = pool;
    tree->free = NULL;
    tree->start = NULL;
    tree->size = 0;

    tree->root = ngx_radix_alloc(tree); //申请root节点结构
    if (tree->root == NULL) {
        return NULL;
    }

//    预申请一些节点,并加入树
    tree->root->right = NULL;
    tree->root->left = NULL;
    tree->root->parent = NULL;
    tree->root->value = NGX_RADIX_NO_VALUE;

    if (preallocate == 0) {
        return tree;
    }

    /*
     * Preallocation of first nodes : 0, 1, 00, 01, 10, 11, 000, 001, etc.
     * increases TLB hits even if for first lookup iterations.
     * On 32-bit platforms the 7 preallocated bits takes continuous 4K,
     * 8 - 8K, 9 - 16K, etc.  On 64-bit platforms the 6 preallocated bits
     * takes continuous 4K, 7 - 8K, 8 - 16K, etc.  There is no sense to
     * to preallocate more than one page, because further preallocation
     * distributes the only bit per page.  Instead, a random insertion
     * may distribute several bits per page.
     *
     * Thus, by default we preallocate maximum
     *     6 bits on amd64 (64-bit platform and 4K pages)
     *     7 bits on i386 (32-bit platform and 4K pages)
     *     7 bits on sparc64 in 64-bit mode (8K pages)
     *     8 bits on sparc64 in 32-bit mode (8K pages)
     */

    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;
    //预申请空间并创建若干层的树结点。
    while (preallocate--) {//Intel x86默认创建七层树结点

        key = 0;
        mask >>= 1;
        mask |= 0x80000000;

        do {
            if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
                != NGX_OK)
            {
                return NULL;
            }

            key += inc;

        } while (key);

        inc >>= 1;
    }

    return tree;
}

//基树插入方法和一般的二叉树基本一致,需要注意的是分支的选择方式:
//如果这一位是1,则进入右子树;否则进入左子树;直到剩下的字节全为0为止。
//如00010000,只要走到第4层的1为止,其它的层不用走。
//这段代码非常复杂,因为分了三个步骤,首先在已经建立的节点中游动,然后再创建需要的子节点,并设置节点的值。
//假如原来的树只有6层,要插入一个新的节点 0x 000 000 11,当走到第6层,即第6个0时,已经没有子节点了,所以退出循环,
//然后进入第二个while循环,创建下面两层相应的节点,最后设置节点的值。
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;

    node = tree->root;
    next = tree->root;

    while (bit & mask) {//1。定位key对应的节点
        if (key & bit) {//bit位是1,则进入右结点
            next = node->right;

        } else {//bit位是0,则进入左结点
            next = node->left;
        }

        if (next == NULL) {//下面的树结构尚未创建,则退出循环
            break;
        }

        bit >>= 1;//进入下一位
        node = next;
    }

    if (next) {//2。要插入的key已经存在于树结构中了,则只要设置值即可
        if (node->value != NGX_RADIX_NO_VALUE) {//如果这个值已经设置,表示这个key已经插入了,则插入失败
            return NGX_BUSY;
        }

        node->value = value;//设置该节点的值,完成插入。
        return NGX_OK;
    }
    //3。如果要插入的节点尚未创建,则创建相应的树结构
    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;
}

//和二叉树的删除不同的是, nginx 的基树删除可能导致祖先同时被删除。
//如果当前有两个元素,一个key=0101,一个key=01,则删除key=01的元素时,因为这个节点的右子树还有效,
//所以只要将 key=01 的节点的值设为NO_VALUE即可。
//但是如果当前只有一个元素,且key=0001,则基树有4层节点,但是当这个元素被删除时,所有的节点都没有用了,
//nginx 就会将这个第四层的节点删除,同时不停的向上删除无用的父节点,直到所有的节点都被删除为止。
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;

    while (node && (bit & mask)) {//定位到要删除的节点
        if (key & bit) {
            node = node->right;

        } else {
            node = node->left;
        }

        bit >>= 1;
    }

    if (node == NULL) {//如果要删除的节点不存在,则返回错误。
        return NGX_ERROR;
    }

    if (node->right || node->left) {//如果有子节点存在,则设置该节点为NO_VALUE
        if (node->value != NGX_RADIX_NO_VALUE) {
            node->value = NGX_RADIX_NO_VALUE;
            return NGX_OK;
        }

        return NGX_ERROR;
    }

    for ( ;; ) {//如果没有子节点,则递归删除空的父节点。
        if (node->parent->right == node) {
            node->parent->right = NULL;

        } else {
            node->parent->left = NULL;
        }

        node->right = tree->free;//give it back to tree's free list
        tree->free = node;

        node = node->parent;//up one step

        if (node->right || node->left) {
            break;
        }

        if (node->value != NGX_RADIX_NO_VALUE) {
            break;
        }

        if (node->parent == NULL) {
            break;
        }
    }

    return NGX_OK;
}

//和二叉树的查找相似,对于这个key从高到底的每个字节,如果是0则进入左节点,如果是1则进入右节点。
//如 0011,则返回root->left->left->right->right的值。
//和二叉树不同的是,nginx 的基树并不一定返回第32层节点的值,而是返回离根节点最近的祖先节点的值。
//如 0011,不一定返回root->l->l->r->r的值,如果root->l->l->r->r无值,则取root->l->l->r的值,
//如果root->l->l->r无值,则取 root->l->l的值,如果root->l->l也无值,则取root->l也无值,则取root的的值,如果 root也无值,则返回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) {//如果该bit=1,则进入右子树
            node = node->right;

        } else {//如果该bit=0,则进入左子树。
            node = node->left;
        }

        bit >>= 1;
    }

    return value;
}

//问题:
//nginx 的基树的find方法,返回的不一定是确切节点的值,而有可能是祖先节点的值,这是nginx期望的?还是个bug呢?
//在 nginx 的源代码中查找调用 ngx_radix32tree_find 方法的代码,发现这个函数只出现在 http/modules/ngx_http_geo_module.c 中,用来查找网络号:
//vv = (ngx_http_variable_value_t *)
//               ngx_radix32tree_find(ctx->u.tree, ngx_http_geo_addr(r, ctx));
//灰常像个bug
//
//代码优化:
//nginx 的基树插入操作的代码太过复杂,因为它将遍历己创建节点和未创建节点的过程分别放在两个while循环中,其实 nginx 的基树与一般的二叉树没什么区别。所以可以采取一般二叉树的插入方法,将两个while循环放到一起,这样代码的可读性会有很在的提升:
//原来的代码44行,现在的代码35行,原来是while+if+while的结构,现在是一个while的结构,优化结果还是很令人欣慰的。
//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;//highest bit
//    node = tree->root;
//    next = tree->root;
//    while (bit & mask) {
//        if (key & bit) {
//            next = node->right;
//        } else {
//            next = node->left;
//        }
//        if (next == NULL) {//create if not present but need to be.
//            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;
//    }
//    if (node->value != NGX_RADIX_NO_VALUE) {//already used
//        return NGX_BUSY;
//    }
//    node->value = value;
//    return NGX_OK;
//}

//这里有个小问题,start块太小的时候,申请一个新的块,原来的一小块就丢掉了,成为一个小空洞。这个小空洞的释放留给tree的pool来管理,这也体现了 nginx 设计的的一个方法学:
//内存的释放由内存池pool来负责,具体的数据结构则只管申请,既不释放,也不担心内存空洞的问题。
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;
        return p;
    }

    //如果free链表为空,则从start内存块中切一块出来
    if (tree->size < sizeof(ngx_radix_node_t)) {
        tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);//如果start内存块太小,则从内存池中申请一块新的内存块给start
        if (tree->start == NULL) {
            return NULL;
        }

        tree->size = ngx_pagesize;
    }
    //从start内存块中切一块
    p = tree->start;
    tree->start += sizeof(ngx_radix_node_t);
    tree->size -= sizeof(ngx_radix_node_t);

    return p;
}


阅读(2127) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

谁的芷煜2013-02-05 15:30:40

最近用到ngx_radix_tree_t,我都不知道是什么,看了你的文章才大体有个概念,很感谢你啊