1. 队列结构
nginx的队列是由具有头节点的双向循环链表实现的,每一个节点结构为ngx_queue_t,定义如下。
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s { //队列结构
ngx_queue_t *prev;
ngx_queue_t *next;
};
其中,sizeof(ngx_queue_t)=8。
从队列结构定义可以看出,nginx的队列结构里并没有其节点的数据内容。
2. 队列操作//初始化队列
ngx_queue_init(q)
//判断队列是否为空
ngx_queue_empty(h)
//在头节点之后插入新节点
ngx_queue_insert_head(h, x)
//在尾节点之后插入新节点
ngx_queue_insert_tail(h, x)
//删除节点x
ngx_queue_remove(x)
//分割队列
ngx_queue_split(h, q, n)
//链接队列
ngx_queue_add(h, n)
//获取队列的中间节点
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*))
其中,插入节点、取队列头、取队列尾等操作由宏实现,获取中间节点、排序等操作由函数实现。下面简单介绍。
2.1 在头节点之后插入
在头节点之后插入操作由宏ngx_queue_insert_head完成,如下。
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
画出该操作的逻辑图,如下。
图中虚线表示被修改/删除的指针,蓝色表示新修改/增加的指针。下同。
2.2 在尾节点之后插入在尾节点之后插入操作由宏ngx_queue_insert_tail完成,如下。
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
该操作的逻辑图如下。
2.3 删除节点
在尾节点之后插入操作由宏ngx_queue_remove完成,如下。
#if (NGX_DEBUG)#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next; \
(x)->prev = NULL; \
(x)->next = NULL
#else
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next
#endif
该操作的逻辑图如下。
2.4 分割队列
分割队列操作由宏ngx_queue_split完成,如下。
#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;
该宏有3个参数,h为队列头(即链表头指针),将该队列从q节点将队列(链表)分割为两个队列(链表),q之后的节点组成的新队列的头节点为n,图形演示如下。
2.5 链接队列
链接队列由宏ngx_queue_add完成,操作如下。
#define ngx_queue_add(h, n) \(h)->prev->next = (n)->next; \
(n)->next->prev = (h)->prev; \
(h)->prev = (n)->prev; \
(h)->prev->next = h;
其中,h、n分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。演示图形如下。
宏ngx_queue_split和ngx_queue_add只在http模块locations相关操作中使用,在后续的讨论http模块locations相关操作时再详细叙述。
2.6 获取中间节点
中间节点,若队列有奇数个(除头节点外)节点,则返回中间的节点;若队列有偶数个节点,则返回后半个队列的第一个节点。操作如下。
/** find the middle queue element if the queue has odd number of elements
* or the first element of the queue’s second part otherwise
*/
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);
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;
}
}
}
为什么该算法能够找到中间节点?
——注意代码中的next指针,其每次均会后移两个位置(节点),而middle指针每次后移一个位置(节点)。演示图形如下。
2.7 队列排序
队列排序采用的是稳定的简单插入排序方法,即从第一个节点开始遍历,依次将当前节点(q)插入前面已经排好序的队列(链表)中,下面程序中,前面已经排好序的队列的尾节点为prev。操作如下。
/* the stable insertion sort */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); //prev指针前移
} while (prev != ngx_queue_sentinel(queue));
ngx_queue_insert_after(prev, q); //将q插入prev节点之后(此处即为简单插入)
}
}
该排序操作使用前面介绍的宏来完成其插入动作,只是一些简单的修改指针指向的操作,效率较高。关于该操作的例子,请参考后文的内容。
2.8 如何获取队列节点数据
由队列基本结构和以上操作可知,nginx的队列操作只对链表指针进行简单的修改指向操作,并不负责节点数据空间的分配。因此,用户在使用nginx队列时,要自己定义数据结构并分配空间,且在其中包含一个ngx_queue_t的指针或者对象,当需要获取队列节点数据时,使用ngx_queue_data宏,其定义如下。
#define ngx_queue_data(q, type, link) \(type *) ((u_char *) q – offsetof(type, link))
由该宏定义可以看出,通过queue在节点中偏移,可以求出节点的起始地址。
3. 一个例子
/**
* ngx_queue_t test
*/
#include <stdio.h>
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_palloc.h"
#include "ngx_queue.h"
//2-dimensional point (x, y) queue structure
typedef struct
{
int x;
int y;
} my_point_t;
typedef struct
{
my_point_t point;
ngx_queue_t queue;
ngx_str_t app_name;
ngx_int_t app_cnt;
} my_point_queue_t;
volatile ngx_cycle_t *ngx_cycle;
#if 1
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const char *fmt, ...)
{
(void)level;
(void)log;
(void) err;
(void)fmt;
}
#endif
#if 1
void dump_pool(ngx_pool_t* pool)
{
while (pool)
{
printf("pool = 0x%p\n", pool);
printf(" .d\n");
printf(" .last = 0x%p\n", pool->d.last);
printf(" .end = 0x%p\n", pool->d.end);
printf(" .next = 0x%p\n", pool->d.next);
printf(" .failed = %d\n", (int) pool->d.failed);
printf(" .max = %d\n", (int)pool->max);
printf(" .current = 0x%p\n", pool->current);
printf(" .chain = 0x%p\n", pool->chain);
printf(" .large = 0x%p\n", pool->large);
printf(" .cleanup = 0x%p\n", pool->cleanup);
printf(" .log = 0x%p\n", pool->log);
printf("available pool memory = %d\n\n",(int) (pool->d.end - pool->d.last));
pool = pool->d.next;
}
}
#endif
void dump_queue_from_head(ngx_queue_t *que)
{
ngx_queue_t *q = ngx_queue_head(que);
printf("(0x%p: (0x%p, 0x%p)) <==> \n", que, que->prev, que->next);
for (; q != ngx_queue_sentinel(que); q = ngx_queue_next(q))
{
my_point_queue_t *point = ngx_queue_data(q, my_point_queue_t, queue);
printf("(%p: (%-2d, %-2d), app_name: %.*s, app_value: %d, %p: (%p, %p)) <==> \n",
point,
point->point.x,
point->point.y,
(int) point->app_name.len,
point->app_name.data,
(int) point->app_cnt,
&point->queue,
point->queue.prev,
point->queue.next);
}
}
void dump_queue_from_tail(ngx_queue_t *que)
{
ngx_queue_t *q = ngx_queue_last(que);
printf("(0x%p: (0x%p, 0x%p)) <==> \n", que, que->prev, que->next);
for (; q != ngx_queue_sentinel(que); q = ngx_queue_prev(q))
{
my_point_queue_t *point = ngx_queue_data(q, my_point_queue_t, queue);
printf("(0x%p: (%-2d, %-2d), 0x%p: (0x%p, 0x%p)) <==> \n", point, point->point.x,
point->point.y, &point->queue, point->queue.prev, point->queue.next);
}
}
//sort from small to big
ngx_int_t my_point_cmp(const ngx_queue_t* lhs, const ngx_queue_t* rhs)
{
my_point_queue_t *pt1 = ngx_queue_data(lhs, my_point_queue_t, queue);
my_point_queue_t *pt2 = ngx_queue_data(rhs, my_point_queue_t, queue);
if (pt1->point.x < pt2->point.x)
return 0;
else if (pt1->point.x > pt2->point.x)
return 1;
else if (pt1->point.y < pt2->point.y)
return 0;
else if (pt1->point.y > pt2->point.y)
return 1;
return 1;
}
#define Max_Num 6
int main()
{
ngx_pool_t *pool;
ngx_queue_t myque;
my_point_queue_t *point;
my_point_t points[Max_Num] = {
{10, 1}, {20, 9}, {9, 9}, {90, 80}, {5, 3}, {50, 20}
};
ngx_str_t app_names[] = {
{4, (u_char*)"app1"},
{4, (u_char*)"app2"},
{8, (u_char*)"app_test"},
{4, (u_char*)"app4"},
{4,(u_char*) "app5"},
{5, (u_char*)"app_6"},
ngx_null_string
};
int i;
pool = ngx_create_pool(1024, NULL);
dump_pool(pool);
//myque = ngx_palloc(pool, sizeof(ngx_queue_t)); //alloc a queue head
ngx_queue_init(&myque); //init the queue
//insert some points into the queue
for (i = 0; i < Max_Num; i++)
{
point = (my_point_queue_t *) ngx_palloc(pool, sizeof(my_point_queue_t));
point->point.x = points[i].x;
point->point.y = points[i].y;
point->app_name = app_names[i];
point->app_cnt = i;
ngx_queue_init(&point->queue);
//insert this point into the points queue
ngx_queue_insert_head(&myque, &point->queue);
}
dump_queue_from_head(&myque);
dump_pool(pool);
ngx_destroy_pool(pool);
return 0;
}
本文编写的 makefile 文件如下。
CXX = gcc
CXXFLAGS += -g -Wall -Wextra
NGX_ROOT = /usr/nginx-1.4.6
TARGETS = ngx_queue_test
TARGETS_C_FILE = $(TARGETS).c
CLEANUP = rm -f $(TARGETS) *.o
all: $(TARGETS)
clean:
$(CLEANUP)
CORE_INCS = -I. \
-I$(NGX_ROOT)/src/core \
-I$(NGX_ROOT)/src/event \
-I$(NGX_ROOT)/src/event/modules \
-I$(NGX_ROOT)/src/os/unix \
-I$(NGX_ROOT)/objs \
-I$(NGX_ROOT)/src/proc \
-I$(NGX_ROOT)/pcre-8.31
NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o
NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o
NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o
NGX_QUEUE = $(NGX_ROOT)/objs/src/core/ngx_queue.o
$(TARGETS): $(TARGETS_C_FILE)
$(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $(NGX_QUEUE) $^ -o $@
3.3 运行结果
pool = 0x0x12dd010
.d
.last = 0x0x12dd060
.end = 0x0x12dd410
.next = 0x(nil)
.failed = 0
.max = 944
.current = 0x0x12dd010
.chain = 0x(nil)
.large = 0x(nil)
.cleanup = 0x(nil)
.log = 0x(nil)
available pool memory = 944
(0x0x7fff453fe050: (0x0x12dd068, 0x0x12dd158)) <==>
(0x12dd150: (50, 20), app_name: app_6, app_value: 5, 0x12dd158: (0x7fff453fe050, 0x12dd128)) <==>
(0x12dd120: (5 , 3 ), app_name: app5, app_value: 4, 0x12dd128: (0x12dd158, 0x12dd0f8)) <==>
(0x12dd0f0: (90, 80), app_name: app4, app_value: 3, 0x12dd0f8: (0x12dd128, 0x12dd0c8)) <==>
(0x12dd0c0: (9 , 9 ), app_name: app_test, app_value: 2, 0x12dd0c8: (0x12dd0f8, 0x12dd098)) <==>
(0x12dd090: (20, 9 ), app_name: app2, app_value: 1, 0x12dd098: (0x12dd0c8, 0x12dd068)) <==>
(0x12dd060: (10, 1 ), app_name: app1, app_value: 0, 0x12dd068: (0x12dd098, 0x7fff453fe050)) <==>
pool = 0x0x12dd010
.d
.last = 0x0x12dd180
.end = 0x0x12dd410
.next = 0x(nil)
.failed = 0
.max = 944
.current = 0x0x12dd010
.chain = 0x(nil)
.large = 0x(nil)
.cleanup = 0x(nil)
.log = 0x(nil)
available pool memory = 656
Reference