nginx源码分析之数据结构:ngx__quque_t
ngx_queue是nginx中的双端队列,该双端队列为了满足通用性,整个结构中没有指向数据节点的部分。
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev; //指向前一个节点
ngx_queue_t *next; //指向后一个节点
};
除了定义节点类型之外,ngx_queue.h头文件还定义相关操作的宏,如下所示:
//双端链表节点的初始化
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
//判断双端链表是否为空,当头节点的prev指向自身的时候,说明没有有效节点
#define ngx_queue_empty(h) \
(h == (h)->prev)
//插入x到双端链表的头节点
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
//这个操作和头插一样
#define ngx_queue_insert_after ngx_queue_insert_head
//插入到双端链表的末尾
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
//得到双端链表的头部
#define ngx_queue_head(h) \
(h)->next
//得到双端链表的尾部
#define ngx_queue_last(h) \
(h)->prev
//得到双端链表的哨兵,其实就是“头节点”本身
#define ngx_queue_sentinel(h) \
(h)
//得到当前节点的下一个节点
#define ngx_queue_next(q) \
(q)->next
//得到当前节点的上一个节点
#define ngx_queue_prev(q) \
(q)->prev
//删除当前节点的头节点
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next; \
(x)->prev = NULL; \
(x)->next = NULL
//该操作较为复杂,其实是对双端链表的一个分割操作,分割之后的结果是:
//1.h到q之前的节点是拆分的前部分;
//2.n成为了后半部分的“头节点”,q是其真实的第一个节点;
//such as: h ----> (其他节点)---->q - 1 ; n ----> q ----> (其他节点)
#define ngx_queue_split(h, q, n) \
(n)->prev = (h)->prev; \
(n)->prev->next = n; \
(n)->next = q; \
(h)->prev = (q)->prev; \
(h)->prev->next = h; \
(q)->prev = n;
//合并两个链表
#define ngx_queue_add(h, n) \
(h)->prev->next = (n)->next; \
(n)->next->prev = (h)->prev; \
(h)->prev = (n)->prev; \
(h)->prev->next = h;
//获取数据的起始地址,因为一般情况下我们把数据区域放在前边,而把双端链表的节点//放在末尾,则需要这个转换获得整个结构的起始位置
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q – offsetof(type, link))
除了上述的宏操作之外,头文件中还包含了对双端链表排序和获取中间节点的操作:
ngx_queue_t *ngx_queue_middle(ngx_queue_t *queue);
void ngx_queue_sort(ngx_queue_t *queue,
ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *));
其中的queue_sort函数中的cmp是一个函数指针,指向了用户指定的比较方式。
上述两个接口的实现在ngx_queue.c中进行了定义:
ngx_queue_t * ngx_queue_middle(ngx_queue_t *queue)
{
ngx_queue_t *middle, *next;
middle = ngx_queue_head(queue);
//当前双端链表中只有一个节点
if (middle == ngx_queue_last(queue)) {
return middle;
}
next = ngx_queue_head(queue);
//middle指针每次向后移动一个节点,next每次向后移动两个节点,当next
//指向末尾节点时,返回middle,它就是该循环链表的中间节点。
for ( ;; ) {
middle = ngx_queue_next(middle);
next = ngx_queue_next(next);
if (next == ngx_queue_last(queue)) {
return middle;
}
next = ngx_queue_next(next);
if (next == ngx_queue_last(queue)) {
return middle;
}
}
}
//双端链表的排序
void ngx_queue_sort(ngx_queue_t *queue,
ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *))
{
ngx_queue_t *q, *prev, *next;
q = ngx_queue_head(queue);
//只有一个节点不需要排序
if (q == ngx_queue_last(queue)) {
return;
}
//采用标准的插入排序方式对双端链表排序
for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) {
prev = ngx_queue_prev(q);
next = ngx_queue_next(q);
ngx_queue_remove(q);
do {
if (cmp(prev, q) <= 0) {
break;
}
prev = ngx_queue_prev(prev);
} while (prev != ngx_queue_sentinel(queue));
ngx_queue_insert_after(prev, q);
}
小结:
双端链表的申请和释放并没有和nginx的内存池有任何关联,但是可以通过在一般节点中添加双端链表节点信息方便我们对于节点的操作。原理也是非常的易于理解。