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 是一种链式存储和顺序储存相结合的链表结构,并不是像传统的链表结构。

posted @ 2012-06-10 10:40  Java研究者  阅读(3232)  评论(2编辑  收藏  举报