TAILQ隊列實現原理


tailq隊列實現原理

TAILQ隊列是FreeBSD內核中的一種隊列數據結構,主要是把隊列頭抽象成一個單獨的結構體。它實現在Linux queue中。

queue 簡介

可以include <sys/queue.h>后直接使用。queue 分為 SLIST、LIST、STAILQ、TAILQ、CIRCLEQ 。queue 的所有源碼都是宏定義,因此完全包含於queue.h當中,無需編譯為庫文件。

可以從toolchains或者系統路徑/usr/include/x86_64-linux-gnu/sys/queue.h找到實現。

 

SLIST:

SLIST 是Singly-linked List 的縮寫,意為單向無尾鏈表。

 

STAILQ:

單向有尾鏈表,節點n為尾節點。

 

LIST:

雙向無尾鏈表

 

TAILQ:

雙向有尾鏈表

 

CIRCLEQ:

雙向循環鏈表

 

TAILQ實現原理:

雙向有尾鏈表,也就是有一個表頭和表尾,表頭指向節點1和尾節點。

描述前一個和下一個元素的結構體:

#define TAILQ_ENTRY(type)                                                   \
struct {                                                                    \
    struct type *tqe_next;      /* next element */                          \
    struct type **tqe_prev;     /* address of previous next element */      \
}

/*tqe_next是指向下一個元素的指針,tqe_prev是指向前一個元素的tqe_next地址,對它解引用后

(*tqe_priv)指向當前元素的地址。*/

如:

struct item{
  int val;
  TAILQ_ENTRY(item) entries;
};

 

隊列頭:

#define    TAILQ_HEAD(name, type)                        \
struct name {                                \
    struct type *tqh_first;    /* first element */            \
    struct type **tqh_last;    /* addr of last next element */        \
}

STAILQ_HEAD(my_tailq,  tailq_entry) queue_head;

 

 我們先看TAILQ_HEAD:

tqh_first為隊列第一個元素的地址;

tqh_last為最后一個元素tqe_next的地址;

tqh_last指向的指針為0;

再看TAILQ_ENTRY:

tqe_next為隊列下一個元素的地址;

tqe_prev為隊列上一個元素tqe_next的地址;

tqe_prev指向的指針為當前元素的地址;

初始化:

 

#define TAILQ_INIT(head) do {                                               \
    (head)->tqh_first = NULL;                                               \
    (head)->tqh_last = &(head)->tqh_first;                                  \
} while (0)

插入元素:

#define TAILQ_INSERT_TAIL(head, elm, field) do {                            \
    (elm)->field.tqe_next = NULL;                                           \
    (elm)->field.tqe_prev = (head)->tqh_last;                               \
    *(head)->tqh_last = (elm);                                              \
    (head)->tqh_last = &(elm)->field.tqe_next;                              \
} while (0)

1.插入1個node時圖解:

1.1 將要插入的node加入到尾部:

  (elm)->field.tqe_next = NULL;                         

  (elm)->field.tqe_prev = (head)->tqh_last;//將要插入的節點prev指向最后一個node      

 

1.2 update head node:

    *(head)->tqh_last = (elm);          

    (head)->tqh_last = &(elm)->field.tqe_next;

 

同理多個元素時尾插:

1.1 將要插入的node加入到尾部:

 

1.2 update head node:

    *(head)->tqh_last = (elm);           //尾節點指向新的尾巴

    (head)->tqh_last = &(elm)->field.tqe_next; //head的last指向新的尾巴

 

刪除元素:

#define TAILQ_REMOVE(head, elm, field) do {                                 \
    if (((elm)->field.tqe_next) != NULL)                                    \
        (elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;      \
    else                                                                    \
        (head)->tqh_last = (elm)->field.tqe_prev;                           \
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;                         \
} while (0)

 

  1. 我們現在要把val=3的elm刪除:

elm中的tqe_next不為空,表示elm不是尾節點。那么

(elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;   

*(elm)->field.tqe_prev = (elm)->field.tqe_next;

這2句執行完后,

 

  然后free掉該elm,

2. 同理再刪除val=2的elm:

 

 然后free掉該elm,

 

3. 我們現在要把val=4的elm刪除:

elm中的tqe_next為空,表示elm是尾節點。那么

(head)->tqh_last = (elm)->field.tqe_prev;               //讓head的last指向新的尾巴        

*(elm)->field.tqe_prev = (elm)->field.tqe_next;    //讓elm的前一個node的next指向該elm的后一個node

 

第一個元素:

#define        TAILQ_FIRST(head)                ((head)->tqh_first)

最后一個元素:

#define        TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))

這個實現看起來有點繞,我們先做一個實驗,

 typedef struct _QUEUE_ITEM {
    int value;
    TAILQ_ENTRY(QUEUE_ITEM) entries;
}QUEUE_ITEM;

TAILQ_HEAD(TAIL_QUEUE, QUEUE_ITEM) queue_head;

int main(int argc, char **argv) {
    QUEUE_ITEM *item[5];
    TAILQ_INIT(&queue_head);

    int i = 0;
    for (i = 0; i < 5; i += 1) {
        item[i] = (struct QUEUE_ITEM*)malloc(sizeof(QUEUE_ITEM));
        item[i]->value = i;
        TAILQ_INSERT_TAIL(&queue_head, item[i], entries);
    }

    for(i = 0; i < 5; i += 1)
    {
            printf("item[%d]: item:%#x, next:%#x,&next:%#x, prev:%#x, *prev:%#x\n",
        i, item[i], item[i]->entries.tqe_next, &(item[i]->entries.tqe_next), item[i]->entries.tqe_prev, *(item[i]->entries.tqe_prev));
    }

    printf("queue_head:%#x, first:%#x, last:%#x\n", &queue_head, queue_head.tqh_first, queue_head.tqh_last);
    printf("last item:%p\n", TAILQ_LAST(&queue_head, TAIL_QUEUE));
}

打印結果如下:

 

可以用圖形來描述:

 

 所以把TAILQ_LAST(&queue_head, TAIL_QUEUE);這句話展開變成

(*(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last))

((struct TAIL_QUEUE*)((&queue_head)->tqh_last))這句話,我們把地址0x601060代入進去得0x602098,即為

 

然后(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last)得到0x602078,

認真的同學此時已經發現,此時對應倒數第二元素的next地址,

最后取(*(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last))得到0x602090,

即為最后一個元素的地址。

總結:這里核心其實就是把最后一個元素的entries成員當成head指針來使用。因為本質上最后一個節點的TAILQ_ENTRY域

和TAILQ_HEAD是同樣的結構。

下一個元素:

#define        TAILQ_NEXT(elm, field)                ((elm)->field.tqe_next)

前一個元素:

#define        TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))

這里和TAILQ_LAST原理一樣,將0x602090代入進去得

 

 然后對*(0x602058)得0x602070,即得到了前一個node的地址。

判斷是否空:

#define        TAILQ_EMPTY(head)                ((head)->tqh_first == NULL)

判斷是否第一個元素:

#define        TAILQ_FIRST(head)                ((head)->tqh_first)

遍歷:

#define        TAILQ_FOREACH(var, head, field)                                        \
for ((var) = ((head)->tqh_first);                                \
(var);                                                        \
(var) = ((var)->field.tqe_next))

倒遍歷:

#define        TAILQ_FOREACH_REVERSE(var, head, headname, field)                \
for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));        \
(var);                                                        \
(var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))

當看懂之前的最后一個元素原理時,倒遍歷的實現是不是超級簡單。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM