源码文件
src/core/ngx_queue.h
src/core/ngx_queue.c
设计思路
Nginx双向链表ngx_queue_t是采用"寄宿"在元素中的包含prev和next的ngx_queue_s来实现的。Linux内核中的page管理同样也使用到了这样的链接结构。linux内核情景分析 这样描述道:linux内核作者将prev和next从具体的“宿主”数据结构中抽象成为一个结构体list_head,这样list_head就可以成为该“宿主”的“连接件”。(kernel:include/linux/list.h , include/linux/mm.h )
采用ngx_quque_t来构建双向链表,可以将链表的链接操作相关的数据结构抽象出来,这样有利于进行链表操作函数的编写。其次,用ngx_queue_t结构串接起来的链表可以是不同类型的数据类型(只要这个数据类型包含ngx_quque_t这个数据结构)。打个不恰当的比喻,不管什么样的物品(数据类型),只要物品上有个孔(ngx_quque_t)我们就能用线(ngx_queue_t构成的链)将这些物品串起来。再者,对于链表而言,进行排序,移动元素等操作只需要修改ngx_queue_t中的相关指针即可,所以也称Nginx的双向链表结构为轻量级链表。
Nginx提供一个非常简单的侵入式双向队列(双向链表),也即向每个元素嵌入数据结构的链接结点。这样做的好处在于:
1. 省去对数据结构所用内存的管理,进一步减少内存碎片;
2. 降低代码复杂度。
另外,通过使用额外的哨兵结点机制,简化了代码。
数据结构
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的队列结构里并没有其节点的数据内容。
/--------------------------------------------------------------------\ | | | ngx_http_location_queue_t ngx_http_location_queue_t | | +-----------------------+ +-----------------------+ | | | ngx_queue_t queue | | ngx_queue_t queue | | | /--+> +--------------------+ <-\ /---+> +--------------------+ <-/ \--|--+- | prev | \--|---+- | prev | | | +--------------------+ | | +--------------------+ | | | next | -----/ | | next | --\ | +--+--------------------+ +--+--------------------+ | | | ... | | ... | | | | | | | | | | | | | | | +-----------------------+ +-----------------------+ | | | \-----------------------------------------------------------------/ 注意点: 1. 图中最左边的queue即为哨兵结点。 如何获取队列节点数据
由队列基本结构和以上操作可知,nginx的队列操作只对链表指针进行简单的修改指向操作,并不负责节点数据空间的分配。
因此,用户在使用nginx队列时,要自己定义数据结构并分配空间,且在其中包含一个ngx_queue_t的指针或者对象,
当需要获取队列节点数据时,使用ngx_queue_data宏,其定义如下。
#define ngx_queue_data(q, type, link) \ (type *) ((u_char *) q – offsetof(type, link))
其实ngx_queue_t只是一个幌子,真正存储在队列中的元素是包含ngx_queue_t成员的结构。
换句话说:如果一个结构想使用队列的方式存储,那么这个结构就要包含ngx_queue_t成员。
offset
这里用到了C语言的一个技巧:offsetof 函数:根据结构的成员的指针定位结构的指针:理解了这个,ngx_queue 就明白了。
例如
struct x_s{
int a;
int b;
} x;
则 &x.b = &x + (&x.b - &x) = &x + offsetof(struct x_s, b);
所以只要一个结构里有ngx_queue成员,就可以根据ngx_queue成员的指针获得这个结构的指针。由该宏定义可以看出,一般定义队列节点结构(该结构类型为type)时,需要将真正的数据放在前面,
而ngx_queue_t结构放在后面,故该宏使用减法计算整个节点结构的起始地址(需要进行类型转换)。
例如:
struct ngx_connection_s { void *data; ngx_event_t *read; ngx_event_t *write; ngx_socket_t fd; ngx_recv_pt recv; ngx_send_pt send; ngx_recv_chain_pt recv_chain; ngx_send_chain_pt send_chain; ngx_listening_t *listening; off_t sent; ngx_log_t *log; ngx_pool_t *pool; struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t addr_text; ngx_str_t proxy_protocol_addr; #if (NGX_SSL) ngx_ssl_connection_t *ssl; #endif struct sockaddr *local_sockaddr; socklen_t local_socklen; ngx_buf_t *buffer; <span style="color:#FF0000;">ngx_queue_t queue;</span> ngx_atomic_uint_t number; ngx_uint_t requests; unsigned buffered:8; unsigned log_error:3; /* ngx_connection_log_error_e */ unsigned unexpected_eof:1; unsigned timedout:1; unsigned error:1; unsigned destroyed:1; unsigned idle:1; unsigned reusable:1; unsigned close:1; unsigned sendfile:1; unsigned sndlowat:1; unsigned tcp_nodelay:2; /* ngx_connection_tcp_nodelay_e */ unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */ unsigned need_last_buf:1; #if (NGX_HAVE_IOCP) unsigned accept_context_updated:1; #endif #if (NGX_HAVE_AIO_SENDFILE) unsigned aio_sendfile:1; unsigned busy_count:2; ngx_buf_t *busy_sendfile; #endif #if (NGX_THREADS) ngx_atomic_t lock; #endif };
ngx_queue_t *q;
ngx_connection_t *c;c = ngx_queue_data(q, ngx_connection_t, queue);
接口函数
以h结点为哨兵的队列称之为h队列。
双向队列的各个操作都很简单,函数名即操作意图:
1. ngx_queue_init(q)初始化哨兵结点,令prev字段和next字段均指向其自身;
2. ngx_queue_empty(q)检查哨兵结点的prev字段是否指向其自身,以判断队列是否为空;
3. ngx_queue_insert_head(h, x)在哨兵结点和第一个结点之间插入新结点x;
4. ngx_queue_insert_after(h, x)是ngx_queue_insert_head的别名;
5. ngx_queue_insert_tail(h, x)在最后一个结点和哨兵结点之间插入新结点;
6. ngx_queue_head(h)获取第一个结点;
7. ngx_queue_last(h)获取最后一个结点;
8. ngx_queue_sentinel(h)获取哨兵结点(即参数h);
9. ngx_queue_next(q)获取下一个结点;
10. ngx_queue_prev(q)获取上一个结点;
11. ngx_queue_remove(x)将结点x从队列中移除;
12. ngx_queue_split(h, q, n)将h为哨兵结点的队列中q结点开始到队尾结点的整个链拆分、链接到空的n队列中,h队列中的剩余结点组成新队列;
13. ngx_queue_add(h, n)将n队列中的所有结点按顺序链接到h队列末尾,n队列清空;
14. ngx_queue_middle(queue)使用双倍步进算法寻找queue队列的中间结点;
15. ngx_queue_sort(queue, cmd)使用插入排序算法对queue队列进行排序,完成后在next方向上为升序,prev方向为降序。