尾队列TAILQ
尾队列TAILQ是FreeBSD/linux内核对双向队列操作的一种抽象,在/usr/include/sys/queue.h文件中实现各种定义。
尾队列能实现操作队列需要的各种操作:插入元素,删除元素,遍历队列等。优点是插入元素很快。
DPDK中使用了大量的尾队列。
1、定义尾队列元素TAILQ_ENTRY
代码中使用TAILQ_ENTRY定义尾队列中的一个元素:
1 2 3 4 5 6 | #define _TAILQ_ENTRY(type, qual) \ struct { \ qual type *tqe_next; /* next element */ \ qual type *qual *tqe_prev; /* address of previous next element */\ } #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) |
展开后即:
1 2 3 4 5 | #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* 指向下一个元素 */ \ struct type **tqe_prev; /* 指向前一个元素的tqe_next变量的地址 */\ } |
这是TAILQ对两个指向前后两个元素指针的抽象,抽象为TAILQ_ENTRY结构体:
tqe_next是指向下一个元素的指针,
tqe_prev是一个二级指针,指针变量的地址,是前一个元素的tqe_next的地址,解引用(*tqe_prev)之后就是前一个元素指向的下一个元素的内存地址
例如,我们声明一个结构体,这个结构体只有一个int型整数,以及指向前一个元素和后一个元素的指针。
1 2 3 4 | struct int_node { int x; TAILQ_ENTRY(int_node) next; }; |
宏展开之后就变成:
1 2 3 4 5 6 7 | struct int_node { int x; struct { struct int_node *tqe_next; struct int_node **tqe_prev; } next; }; |
如图:
但还需要其它手段才能实现数据的存储。
2、创建队列头TAILQ_HEAD
创建队列头的宏TAILQ_HEAD定义如下:
1 2 3 4 5 6 | #define _TAILQ_HEAD(name, type, qual) \ struct name { \ qual type *tqh_first; /* first element */ \ qual type *qual *tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) |
展开后为:
1 2 3 4 5 | #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* 指向第一个元素 */ \ struct type **tqh_last; /* 指向最后一个元素的tqe_next元素的地址 */ \ } |
创建一个名为name的结构体,作为尾队列的头结点。
这个宏实际上使用的时候,会展开成为一个结构体,
tqh_first是一个一级指针,指向队列中的第一个元素。
tqh_last是一个二级指针,它指向最后一个元素中的tqe_next(参考上面的TAILQ_ENTRY)变量的地址,指针的地址就是二级指针。
例如:
1 2 3 4 5 | struct int_node { int x; TAILQ_ENTRY(int_node) next; }; TAILQ_HEAD(qhead, int_node); |
展开后为:
1 2 3 4 | struct qhead { struct int_node *tqh_first; struct int_node **tqh_last; }; |
就创建了一个名为qhead的队列头,队列内容指向的结构体类型为struct int_node。
或者将其用在其它结构体中:
1 2 3 4 | struct s1 { int x; TAILQ_HEAD(, int_node) q; }; |
展开后为:
1 2 3 4 5 6 7 | struct s1 { int x; struct { struct int_node *tqh_first; struct int_node **tqh_last; } q; }; |
3、初始化队列头TAILQ_INIT
1 2 3 4 | #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) |
将头指针tqh->first初始化为NULL,尾指针tqh_last(指向指针的指针)指向头指针tqh_first的地址。
如下图
仍以struct int_node为例:
1 2 3 4 5 6 | struct int_node { int x; TAILQ_ENTRY(int_node) next; }; TAILQ_HEAD(qhead, int_node) queue_head; TAILQ_INIT(&queue_head); |
展开后即:
1 2 3 4 5 6 | struct qhead { struct int_node *tqh_first; struct int_node **tqh_last; } queue_head; (&queue_head)->tqh_first = NULL; (&queue_head)->tqh_last = &(&queue_head)->tqh_first; |
创建了一个名为queue_head的队列头,并对其初始化。
队列指向的结构体为struct int_node。
4、在创建时初始化一个队列头TAILQ_HEAD_INITIALIZER
1 2 | #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } |
仍以struct int_node为例:
1 2 3 4 5 6 | struct int_node { int x; TAILQ_ENTRY(int_node) next; }; TAILQ_HEAD(qhead, int_node); struct qhead queue_head = TAILQ_HEAD_INITIALIZER(queue_head); |
展开后即为:
1 2 3 4 5 | struct qhead queue_head = { NULL, &queue_head.tqh_first }; |
5、插入尾队列尾部TAILQ_INSERT_TAIL
插入元素用TAILQ_INSERT_TAIL宏,由于TAILQ中有一个tqh_last的二级指针,所以插入元素直接插到队尾,仅用O(1)时间。
1 2 3 4 5 6 | #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 (/*CONSTCOND*/0) |
宏定义中第一个元素为队列的头结点,如上文的&queue_head,
第二个元素为要插入的结点,如上文的item,
第三个元素为结点中用TAILQ_ENTRY宏定义的域。
举例说明:
(1)定义
1 2 3 4 5 | struct int_node { int x; TAILQ_ENTRY(int_node) next; }; TAILQ_HEAD(, int_node) queue_head; |
展开后:
1 2 3 4 5 6 7 8 9 10 11 | struct int_node { int x; struct { struct int_node *tqe_next; struct int_node **tqe_prev; } next; }; struct { struct int_node *tqh_first; struct int_node **tqh_last; } queue_head; |
(2)初始化
1 | TAILQ_INIT(&queue_head); |
即:
1 2 | (&queue_head)->tqh_first = NULL; (&queue_head)->tqh_last = &(&s_table)->tqh_first; |
(3)分配一个结点并插入
1 2 3 | struct int_node *item = malloc(sizeof(struct int_node)); item->x = 2; TAILQ_INSERT_TAIL(&queue_head, item, next); |
插入操作展开后即:
1 2 3 4 | item->next.tqe_next = NULL; item->next.tqe_prev = (&queue_head)->tqh_last; *(&queue_head)->tqh_last = item; (&queue_head)->tqh_last = &(item)->next.tqe_next; |
通过图形遂句解析插入操作:
第一句, item->next.tqe_next = NULL;
将新元素(数据部分值为2)的tqe_next指向NULL;
第二句, item->next.tqe_prev = (&queue_head)->tqh_last;
将新元素的tqe_prev的值赋值为(& queue_head)->tqh_last,即原来队列中最后一个元素的tqe_next变量的地址,即当前队列头指向的tqh_first的地址:
第三句, *(&queue_head)->tqh_last = item;
将头结点的tqh_last的值,即原队列最后一个元素的tqe_next指向item;
如果原队列为空,则由于当前为向队列中插入第一个结点,
而此时*(&queue_head)->tqh_last的值就是tqh_first的地址,所以这一句也就等价于:
(&queue_head)->tqh_first = item;
也就是将(&queue_head)->tqh_first指向item。
而如果原队列不为空,则(&queue_head)->tqh_last的值为原队列最后一个结点的tqe_next的地址,
这时就变成了该结点的tqe_next指向新插入的结点,
相当于把新结点插入到原队列尾结点的后面。
第四句: (&queue_head)->tqh_last = &(item)->next.tqe_next;
将新元素的tqe_next的地址赋值给头结点的tqh_last指针(指向指针的指针,二级指针):
6、向队列头部插入新元素TAILQ_INSERT_HEAD
向队列头部插入元素用TAILQ_INSERT_HEAD宏,定义如下:
1 2 3 4 5 6 7 8 9 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (/*CONSTCOND*/0) |
首先将原队列头部指针赋值给新元素的tqe_next,并判断如果此时指向为空,
如果不为空,说明队列中已经包含其他元素,该原队列中的第一个元素的tqe_prev保存新元素的tqe_next变量的地址;
如果为空, 说明新元素是要插入的第一个元素,则直接将新元素tqe_next变量的地址赋值给头结点的tqh_first;
然后,将队列头部指向新元素,
之后,用新元素的tqe_prev保存队列头部tqh_first的地址。
7、删除队列中的指定元素TAILQ_REMOVE
1 2 3 4 5 6 7 8 | #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 (/*CONSTCOND*/0) |
宏定义中的第一个元素是队列的头结点,如前文的&queue_head,
第二个元素是要删除的结点,如前文的item,
第三个元素是创建的TAILQ_ENTYR宏定义的域。
假设没有删除之前的队列如下所示:
假设要删除队列中的第一个元素,即值为2的元素。
宏定义的第一行首先判断当前元素的tqe_next是否为空,
如果不为空,说明当前元素不是队列中的最后一个元素,
就将当前元素的tqe_prev的值赋值给下一个元素的tqe_prev,让其指向自己的上一个元素;
如果为空,说明当前元素是队列中的最后一个元素,
就将队列头的tqh_last的值保存为当前元素的tqe_prev,
也就是将队列头的尾指针指向当前元素的前一个元素。
结果如图如示:
之后,将当前元素的*tqe_prev的值保存为当前元素的tqe_next的地址,
而当前元素的*tqe_prev的值是前一个元素的tqe_next的值,该值保存的是当前元素的tqe_next的地址,
所以这一语句就是将*tqe_prev的值保存为当前元素的下一个元素的tqe_next的地址:
至此,虽然值为2的元素还连在队列中(只有它连接另的元素,别的元素不指向这个要删除的元素),但如果这时遍历队列的话,已经不会遍历该元素了。
释放该结点之后:
8、其他常用宏
8.1 队列的第一个元素TAILQ_FIRST
1 | #define TAILQ_FIRST(head) ((head)->tqh_first) |
8.2 当前元素的下一个元素TAILQ_NEXT
1 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) |
8.3 判断队列是否为空TAILQ_EMPTY
1 | #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) |
8.4 遍历列表中的每个元素TAILQ_FOREACH
1 2 3 4 | #define TAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->tqh_first); \ (var); \ (var) = ((var)->field.tqe_next)) |
就是利用临时变量var来遍历队列中的每个元素。
参考:http://blog.csdn.net/hunanchenxingyu/article/details/8648794
————————————————————
原创文章,转载请注明: 转载自孙希栋的博客
本文链接地址: 《尾队列TAILQ》