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; };
参数说明:elts为array数组中元素的首地址,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_create和ngx_array_init,代码比较简明就不多说了,值得注意的是两者之间的差别,ngx_array_init使用情形是已经存在了ngx_array_t的结构体,而ngx_array_create则从零开始建起,贴出代码:
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; }
代码里面主要就是if和else的逻辑关系,可解释为以下几种情形:
第一,如果array当前已分配的元素个数小于最大分配个数,那么用数组元素首地址a->elts 计算出分配元素的首地址,并返回结果。
第二,如果array中当前已分配元素个数等于最大分配元素个数,并且array所在内存池pool还有空间可分配给新元素,那么对array在本对array进行扩充一个单元,扩充后即变成第一中情形进行处理。
第三,如果array中当前已分配元素个数等于最大分配元素个数,并且array所在内存池pool没有空间可分配给新元素,那么对array大小增大一倍后进行重新分配,并将原来array内容拷贝到新地址空间中,完成后最大容量变成原来的两倍,同第一中情形进行处理。
同样的ngx_array_push_n,也是类似的处理,不再次重复了。ngx_array_destroy数组注销函数,则是对pool池中数据分配段末指针,移动array中允许存储的元素总共需要的空间大小的距离即可实现注销工作。对于此处不多加说明,如有不明白之处请参考前面已写的关于nginx内存池(pool)的分析就很容易明白了。附上代码:
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_create和ngx_list_init ,其类似于前面的array,也是注意下需要使用范围,代码简明不多介绍,唯一注意一点的是list->last = &list->part;它即表示第一个链表的头节点即为本身参数中的part,附上源码:
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_push与array中的同样相似。代码如下:
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,便于理解
图1 ngx_list 结构示意图
我们可以从图中看出,ngx_list 是一种链式存储和顺序储存相结合的链表结构,并不是像传统的链表结构。