• Nginx 源码分析 ngx_array、ngx_list基本数据结构


      应该说大家对这两个数据结构相当熟悉了,因此我们一并将它们进行分析,瞧一瞧nginx是如何实现它们的。在此篇之前,我们已经对nginx 内存池(pool)进行了分析,在此基础上来理解ngnix对它们的实现将变得非常简单,特别是内存池(pool)中的ngx_palloc 函数在这两个结构中多次用到,若不清楚想了解原理的可以看看我前面写的文章,它返回的是在内存池分配好空间了的首地址。

    一、ngx_array 数组:

    struct ngx_array_s {
        void        *elts;
        ngx_uint_t   nelts;
        size_t       size;
        ngx_uint_t   nalloc;
        ngx_pool_t  *pool;
    };

      参数说明:eltsarray数组中元素的首地址,nelts数组中已分配的元素个数,size每个元素大小,nalloc数组容量,pool其所在的内存池。

      能够支持五种函数操作:

      创建数组:

        ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

      数组初始化:

        ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)

      数组注销:

        ngx_array_destroy(ngx_array_t *a);

      添加一个数组元素:

        ngx_array_push(ngx_array_t *a);

      添加n个数组元素:

        ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

      ngx_array_createngx_array_init,代码比较简明就不多说了,值得注意的是两者之间的差别,ngx_array_init使用情形是已经存在了ngx_array_t的结构体,而ngx_array_create则从零开始建起,贴出代码:

    View Code
    static ngx_inline ngx_int_t
    ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
    {
        /*
         * set "array->nelts" before "array->elts", otherwise MSVC thinks
         * that "array->nelts" may be used without having been initialized
         */
    
        array->nelts = 0;
        array->size = size;
        array->nalloc = n;
        array->pool = pool;
    
        array->elts = ngx_palloc(pool, n * size);
        if (array->elts == NULL) {
            return NGX_ERROR;
        }
    
        return NGX_OK;
    }
    
    ngx_array_t *
    ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
    {
        ngx_array_t *a;
    
        a = ngx_palloc(p, sizeof(ngx_array_t));
        if (a == NULL) {
            return NULL;
        }
    
        a->elts = ngx_palloc(p, n * size);
        if (a->elts == NULL) {
            return NULL;
        }
    
        a->nelts = 0;
        a->size = size;
        a->nalloc = n;
        a->pool = p;
    
        return a;
    }

      重点介绍下ngx_array_push函数

    void *
    ngx_array_push(ngx_array_t *a)
    {
        void        *elt, *new;
        size_t       size;
        ngx_pool_t  *p;
    
        if (a->nelts == a->nalloc) {
    
            /* the array is full */
    
            size = a->size * a->nalloc;
    
            p = a->pool;
    
            if ((u_char *) a->elts + size == p->d.last
                && p->d.last + a->size <= p->d.end)
            {
                /*
                 * the array allocation is the last in the pool
                 * and there is space for new allocation
                 */
    
                p->d.last += a->size;
                a->nalloc++;
    
            } else {
                /* allocate a new array */
    
                new = ngx_palloc(p, 2 * size);
                if (new == NULL) {
                    return NULL;
                }
    
                ngx_memcpy(new, a->elts, size);
                a->elts = new;
                a->nalloc *= 2;
            }
        }
    
        elt = (u_char *) a->elts + a->size * a->nelts;
        a->nelts++;
    
        return elt;
    }

      代码里面主要就是ifelse的逻辑关系,可解释为以下几种情形:

      第一,如果array当前已分配的元素个数小于最大分配个数,那么用数组元素首地址a->elts 计算出分配元素的首地址,并返回结果。

      第二,如果array中当前已分配元素个数等于最大分配元素个数,并且array所在内存池pool还有空间可分配给新元素,那么对array在本对array进行扩充一个单元,扩充后即变成第一中情形进行处理。

      第三,如果array中当前已分配元素个数等于最大分配元素个数,并且array所在内存池pool没有空间可分配给新元素,那么对array大小增大一倍后进行重新分配,并将原来array内容拷贝到新地址空间中,完成后最大容量变成原来的两倍,同第一中情形进行处理。

      同样的ngx_array_push_n,也是类似的处理,不再次重复了。ngx_array_destroy数组注销函数,则是对pool池中数据分配段末指针,移动array中允许存储的元素总共需要的空间大小的距离即可实现注销工作。对于此处不多加说明,如有不明白之处请参考前面已写的关于nginx内存池(pool)的分析就很容易明白了。附上代码:

    View Code
    void
    ngx_array_destroy(ngx_array_t *a)
    {
        ngx_pool_t  *p;
    
        p = a->pool;
    
        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
            p->d.last -= a->size * a->nalloc;
        }
    
        if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
            p->d.last = (u_char *) a;
        }
    }

    二、ngx_list 链表:

      理解完ngx_array后,再来看ngx_list 就会发现它们之间有许多类似的地方。首先还是看其中的两个数据结构。

     

    struct ngx_list_part_s {
        void             *elts;
        ngx_uint_t        nelts;
        ngx_list_part_t  *next;
    };

      参数说明:elts指向链表元素地址,nelts为链表中含有元素的个数,next下一个元素地址。

    typedef struct {
        ngx_list_part_t  *last;
        ngx_list_part_t   part;
        size_t            size;
        ngx_uint_t        nalloc;
        ngx_pool_t       *pool;
    } ngx_list_t;

      参数说明:这个结构体是用来管理存在的链表的。last为链表的首地址。part一个管理链表元素的链表结构,size每个元素大小,nalloc链表允许的最多元素个数,pool是所在的内存池。

      如果对参数说明中有疑惑的地方,暂且可以放过继续阅读,读到后文中的图1时,再回头过来看就应该能够明白了。同样的在ngx_list中存在着ngx_list_createngx_list_init ,其类似于前面的array,也是注意下需要使用范围,代码简明不多介绍,唯一注意一点的是list->last = &list->part;它即表示第一个链表的头节点即为本身参数中的part,附上源码:

    View Code
    static ngx_inline ngx_int_t
    ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
    {
        list->part.elts = ngx_palloc(pool, n * size);
        if (list->part.elts == NULL) {
            return NGX_ERROR;
        }
    
        list->part.nelts = 0;
        list->part.next = NULL;
        list->last = &list->part;
        list->size = size;
        list->nalloc = n;
        list->pool = pool;
    
        return NGX_OK;
    }
    ngx_list_t *
    ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
    {
        ngx_list_t  *list;
    
        list = ngx_palloc(pool, sizeof(ngx_list_t));
        if (list == NULL) {
            return NULL;
        }
    
        list->part.elts = ngx_palloc(pool, n * size);
        if (list->part.elts == NULL) {
            return NULL;
        }
    
        list->part.nelts = 0;
        list->part.next = NULL;
        list->last = &list->part;
        list->size = size;
        list->nalloc = n;
        list->pool = pool;
    
        return list;
    }

      另外一个函数ngx_list_pusharray中的同样相似。代码如下:

    View Code
    void *
    ngx_list_push(ngx_list_t *l)
    {
        void             *elt;
        ngx_list_part_t  *last;
    
        last = l->last;
    
        if (last->nelts == l->nalloc) {
    
            /* the last part is full, allocate a new list part */
    
            last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
            if (last == NULL) {
                return NULL;
            }
    
            last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
            if (last->elts == NULL) {
                return NULL;
            }
    
            last->nelts = 0;
            last->next = NULL;
    
            l->last->next = last;
            l->last = last;
        }
    
        elt = (char *) last->elts + l->size * last->nelts;
        last->nelts++;
    
        return elt;
    }

      一个主要的if解释如下,如果最后一个链表中的元素个数达到了最大,那么就需要扩充管理链表的链表;如果没有达到最大就正常分配节点单元。如图示1,便于理解

      

    ngx_list 结构示意图

    图1 ngx_list 结构示意图

      我们可以从图中看出,ngx_list 是一种链式存储和顺序储存相结合的链表结构,并不是像传统的链表结构。

  • 相关阅读:
    将RIP协议配置成单播
    powershell的超级破烂的设置问题。
    netsh trace抓包结合microsoft network monitor 进行分析
    Managing Windows Programs from the Command Line: Tasklist
    windows 7 的个超级工具
    Metasploit开源安全漏洞检测工具
    Assigned Internet Protocol Numbers
    4B/5B编码原理
    PHP在IIS下。
    网络层的一些附属协议
  • 原文地址:https://www.cnblogs.com/jzhlin/p/ngx_list.html
Copyright © 2020-2023  润新知