nginx_图解nginx关键数据结构之ngx_list_t
1、ngx_list_t的定义
ngx_list_t是nginx中封装的链表,与普通链表不同的是,每个链表元素中又挂载了一个连续内存空间,也可以理解为挂载了一个数组。ngx_list_t定义在nginx/src/core/ngx_list.h
文件中,下面来看看代码:
typedef struct ngx_list_part_s ngx_list_part_t;
//链表元素
struct ngx_list_part_s {
void *elts;//指向数组的起始地址
ngx_uint_t nelts;//表示数组中已经使用了多少个元素
ngx_list_part_t *next;//指向下一个链表元素的地址
};
typedef struct {
ngx_list_part_t *last;//指向链表最后一个元素的地址
ngx_list_part_t part;//第一个链表元素
size_t size;//ngx_list_part_t中的单个数组元素所占空间大小
ngx_uint_t nalloc;//ngx_list_part_t中数组的容量capacity
ngx_pool_t *pool;//链表中管理分配的内存池对象
} ngx_list_t;
下图清楚的展示了ngx_list_t的数据结构(假设内存池分配的是连续空间):
2、ngx_list_t提供的接口
ngx_list_t的接口定义在nginx/src/core/ngx_list.c
文件中,下面来看看代码(下面代码中设计到的内存池相关函数在我的另一篇博客中有讲解到,戳这里 》》》nginx_图解nginx关键数据结构内存池 ngx_pool_t)
2.1、 链表创建及初始化
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;
}
if (ngx_list_init(list, pool, n, size) != NGX_OK) {
return NULL;
}
return list;
}
//初始化函数在ngx_list.h中
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;//初始化时已使用0个元素
list->part.next = NULL;//初始化时链表就一个元素
list->last = &list->part;//初始化时尾部元素就是第一个元素
list->size = size;
list->nalloc = n;
list->pool = pool;
return NGX_OK;
}
2.2、向链表中插入新的数组元素
//返回的是新的数组元素应该插入的位置
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 */
//分配ngx_list_part_t结构空间
last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}
//分配nalloc * size字节大小的空间
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;
}
//如果last part没有满,就返回l->size * last->nelts之后的空间地址
//如果满了,那么此时last->nelts == 0,就返回新的空间的开头
elt = (char *) last->elts + l->size * last->nelts;
last->nelts++;
return elt;//成功返回新分配的元素首地址
}
不多bb,直接看图理解上述过程:
(1)last part 未满的情况
(2)last part 已满的情况
应用举例:
ngx_list_t* test_list = ngx_list_create(r->pool, 4, sizeof(ngx_str_t));
if(test_list == NULL){
return NGX_ERROR;
}
ngx_str_t* str = ngx_list_push(test_list);
if(str == NULL){
return NGX_ERROR;
}
str->len = sizeof("hello world");
str->data = "hello world";
2.3、 加餐!遍历接口以及Tengine额外添加的接口
2.3.1、 遍历接口
//the iteration through the list:
part = &list.part;
data = part->elts;
//这里i代表数组中元素的索引
for (i = 0 ;; i++) {
//如果i大于等于nelts,说明当前数组已经遍历完
if (i >= part->nelts) {
//如果part->next == NULL,说明链表已经遍历完,退出循环
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
//... opearte data[i] ...
}
2.3.2、 Tengine额外添加的删除元素接口
#if (T_NGX_IMPROVED_LIST)
//删除数组中某个元素的接口
//需要指定链表ngx_list_t、链表元素ngx_list_part_t、索引ngx_uint_t
static ngx_int_t
ngx_list_delete_elt(ngx_list_t *list, ngx_list_part_t *cur, ngx_uint_t i)
{
u_char *s, *d, *last;
s = (u_char *) cur->elts + i * list->size;
d = s + list->size;
last = (u_char *) cur->elts + cur->nelts * list->size;
while (d < last) {
*s++ = *d++;
}
cur->nelts--;
return NGX_OK;
}
//删除ngx_list_t中某个指定数组元素,需要遍历每个part的数组元素
//并调用删除数组元素的接口
ngx_int_t
ngx_list_delete(ngx_list_t *list, void *elt)
{
u_char *data;
ngx_uint_t i;
ngx_list_part_t *part, *pre;
part = &list->part;
pre = part;
data = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
i = 0;
pre = part;
part = part->next;
data = part->elts;
}
//如果当前part的数组元素i是要删除的元素
if ((data + i * list->size) == (u_char *) elt) {
//如果list的第一个part不等于当前part 且 当前part占用元素为1,直接删除该part
//ngx_list_t至少要有一个part
if (&list->part != part && part->nelts == 1) {
pre->next = part->next;
if (part == list->last) {
list->last = pre;
}
return NGX_OK;
}
//否则就直接删除该元素
return ngx_list_delete_elt(list, part, i);
}
}
return NGX_ERROR;
}
#endif
同样,图解删除操作:
3、Reference
3.1 深入理解nginx(第二版) 陶辉 机械工业出版社
3.2 Tengine社区:https://tengine.taobao.org/