Nginx源码阅读-队列

前言

这个queue从字面上来说是队列,但是阅读完之后感觉是一个双向循环链表,暂时也不知道怎么称呼了,后续能确定了之后再说吧。

Nginx中的队列也是一个包含哨兵节点的双向循环链表,和我们从教材中认识的双向循环链表有一些不同。在Nginx中,双向循环链表的结构体中只有两个数据成员,分别是指向前一个结构体的指针和指向后一个结构体多的指针,并没有一个数据成员是用来保存数据的。这是因为在Nginx中,队列都是作为其他结构中的一个数据成员出现的,相关数据存储都在和其他结构体中体现的。

数据结构定义

1
2
3
4
5
6
typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
ngx_queue_t *prev; //前一个结构体
ngx_queue_t *next; //后一个结构体
};

主要方法

ngx_queue_init

初始化

1
2
3
#define ngx_queue_init(q)                                                     \
(q)->prev = q; \
(q)->next = q

ngx_queue_empty

判断队列是否为空

1
2
#define ngx_queue_empty(h)                                                    \
(h == (h)->prev)

ngx_queue_insert_head

从队列头部插入

1
2
3
4
5
#define ngx_queue_insert_head(h, x)                                           \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x

ngx_queue_insert_after

从队列头部插入

1
#define ngx_queue_insert_after   ngx_queue_insert_head

ngx_queue_insert_tail

尾部插入

1
2
3
4
5
#define ngx_queue_insert_tail(h, x)                                           \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x

ngx_queue_head

获取队列第一个元素

1
2
3
4
5
6
7
8
9
10
11
#define ngx_queue_head(h)                                                     \
(h)->next
```

#### ngx_queue_last

获取队列最后一个元素

```c
#define ngx_queue_last(h) \
(h)->prev

ngx_queue_sentinel

获取队列的哨兵元素

1
2
#define ngx_queue_sentinel(h)                                                 \
(h)

ngx_queue_next

获取队列的下一个元素

1
2
#define ngx_queue_next(q)                                                     \
(q)->next

ngx_queue_prev

获取队列的前一个元素

1
2
#define ngx_queue_prev(q)                                                     \
(q)->prev

ngx_queue_remove

从队列中删除元素

1
2
3
#define ngx_queue_remove(x)                                                   \
(x)->next->prev = (x)->prev; \
(x)->prev->next = (x)->next

ngx_queue_split

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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;
```

#### ngx_queue_add

把n添加到h的末尾

```c
#define ngx_queue_add(h, n) \
(h)->prev->next = (n)->next; \
(n)->next->prev = (h)->prev; \
(h)->prev = (n)->prev; \
(h)->prev->next = h;

ngx_queue_data

获取队列节点对应的数据节点

1
2
#define ngx_queue_data(q, type, link)                                         \
(type *) ((u_char *) q - offsetof(type, link))

示例

下面的示例图是结构体ngx_http_file_cache_node_t的示意图,queue作为其第二个数据成员,其相对ngx_http_file_cache_node_t的偏移量就是结构体ngx_rbtree_node_t大小。因此把queue的地址减去这个偏移量就得到了ngx_http_file_cache_node_t的地址了。

拓展

在c语言中offsetof的作用就是求结构体中某一成员的相对偏移量的,其实现如下,就是把地址0作为结构体的起始地址,然后获取相应的成员的地址就是我们要的偏移量了。

1
#define offsetof(type,member) ((size_t) &(((type*)0)->member))

ngx_queue_middle

获取队列处于中间位置的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
}
}
}

ngx_queue_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);

        } while (prev != ngx_queue_sentinel(queue));

        ngx_queue_insert_after(prev, q);
    }
}