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)
- 我們現在要把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)))
當看懂之前的最后一個元素原理時,倒遍歷的實現是不是超級簡單。