Nginx数据结构——ngx_queue_t

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;


其中,hn分别为两个队列的指针,即头节点指针,该操作将n队列链接在h队列之后。演示图形如下。


ngx_queue_splitngx_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

nginx源码分析—队列结构ngx_queue_t



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值