您的位置:首页 > 理论基础 > 数据结构算法

Nginx数据结构——ngx_queue_t

2016-07-06 20:12 344 查看
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*))


其中,插入节点、取队列头、取队列尾等操作由宏实现,获取中间节点、排序等操作由函数实现。下面简单介绍。


[b]2.1 在头节点之后插入
[/b]

在头节点之后插入操作由宏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 $@




[b]3.3 运行结果
[/b]

[b]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
[/b]



[b]Reference
[/b]

[b]nginx源码分析—队列结构ngx_queue_t
[/b]



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: