天天看點

9.nginx源碼分析之資料結構:ngx__queue_tnginx源碼分析之資料結構:ngx__quque_t

nginx源碼分析之資料結構:ngx__quque_t

ngx_queue是nginx中的雙端隊列,該雙端隊列為了滿足通用性,整個結構中沒有指向資料節點的部分。

typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;   //指向前一個節點
    ngx_queue_t  *next;   //指向後一個節點
};
           

除了定義節點類型之外,ngx_queue.h頭檔案還定義相關操作的宏,如下所示:

//雙端連結清單節點的初始化
#define ngx_queue_init(q)                                                     \
    (q)->prev = q;                                                            \
    (q)->next = q


//判斷雙端連結清單是否為空,當頭節點的prev指向自身的時候,說明沒有有效節點
#define ngx_queue_empty(h)                                                    \
    (h == (h)->prev)


//插入x到雙端連結清單的頭節點
#define ngx_queue_insert_head(h, x)                                           \
    (x)->next = (h)->next;                                                    \
    (x)->next->prev = x;                                                      \
    (x)->prev = h;                                                            \
    (h)->next = x

//這個操作和頭插一樣
#define ngx_queue_insert_after   ngx_queue_insert_head

//插入到雙端連結清單的末尾
#define ngx_queue_insert_tail(h, x)                                           \
    (x)->prev = (h)->prev;                                                    \
    (x)->prev->next = x;                                                      \
    (x)->next = h;                                                            \
    (h)->prev = x

//得到雙端連結清單的頭部
#define ngx_queue_head(h)                                                     \
    (h)->next

//得到雙端連結清單的尾部
#define ngx_queue_last(h)                                                     \
    (h)->prev

//得到雙端連結清單的哨兵,其實就是“頭節點”本身
#define ngx_queue_sentinel(h)                                                 \
    (h)

//得到目前節點的下一個節點
#define ngx_queue_next(q)                                                     \
    (q)->next

//得到目前節點的上一個節點
#define ngx_queue_prev(q)                                                     \
    (q)->prev

//删除目前節點的頭節點
#define ngx_queue_remove(x)                                                   \
    (x)->next->prev = (x)->prev;                                              \
    (x)->prev->next = (x)->next;                                              \
    (x)->prev = NULL;                                                         \
    (x)->next = NULL

//該操作較為複雜,其實是對雙端連結清單的一個分割操作,分割之後的結果是:
//h到q之前的節點是拆分的前部分;
//n成為了後半部分的“頭節點”,q是其真實的第一個節點;
//such as: h ----> (其他節點)---->q -   ;  n ----> q ----> (其他節點)
#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;


//合并兩個連結清單
#define ngx_queue_add(h, n)                                                   \
    (h)->prev->next = (n)->next;                                              \
    (n)->next->prev = (h)->prev;                                              \
    (h)->prev = (n)->prev;                                                    \
    (h)->prev->next = h;


//擷取資料的起始位址,因為一般情況下我們把資料區域放在前邊,而把雙端連結清單的節點//放在末尾,則需要這個轉換獲得整個結構的起始位置
#define ngx_queue_data(q, type, link)                 \  
    (type *) ((u_char *) q – offsetof(type, link)) 
           

除了上述的宏操作之外,頭檔案中還包含了對雙端連結清單排序和擷取中間節點的操作:

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

其中的queue_sort函數中的cmp是一個函數指針,指向了使用者指定的比較方式。

上述兩個接口的實作在ngx_queue.c中進行了定義:

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

    //middle指針每次向後移動一個節點,next每次向後移動兩個節點,當next
    //指向末尾節點時,傳回middle,它就是該循環連結清單的中間節點。
    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;
        }
    }
}


//雙端連結清單的排序
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) <= ) {
                break;
            }

            prev = ngx_queue_prev(prev);

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

        ngx_queue_insert_after(prev, q);
    }
           

小結:

雙端連結清單的申請和釋放并沒有和nginx的記憶體池有任何關聯,但是可以通過在一般節點中添加雙端連結清單節點資訊友善我們對于節點的操作。原理也是非常的易于了解。

繼續閱讀