文章目录
TAILQ 介绍
TAILQ
队列是 FreeBSD
内核中的一种队列数据结构,在一些著名的开源库中(如 DPDK
、libevent
)有广泛的应用。
TAILQ 和Linux
中list
的组织方式不一样,后者是单纯地将 struct list_head
作为链表的挂接点,并没有用户的信息,它们的差别如下图。
Linux
中的list
只将struct list_head
作为用户元素的挂接点,因此在正向遍历链表时,需要使用container_of
这类接口才能获取用户的数据,而TAILQ
由于tqe_next
指针直接指向用户元素,所以理论上,正向遍历TAILQ
比list
更快.
头文件
头文件来自我最近看的项目:apache-mynewt-core-1.9.0\sys\sys\include\sys\queue.h
仅截取部分。
// "bsd_queue.h" 为了测试,我重新起了名字
/*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD: src/sys/sys/queue.h,v 1.32.2.7 2002/04/17 14:21:02 des Exp $
*/
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
/*
* Tail queue functions.
*/
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
} while (0)
举例
来自 https://manpages.courier-mta.org/htmlman3/tailq.3.html
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "bsd_queue.h" //没有用 Linux 的,换成了我自己的
struct entry {
int data;
TAILQ_ENTRY(entry) entries; /* Tail queue */
};
TAILQ_HEAD(tailhead, entry);
int
main(void)
{
struct entry *n1, *n2, *n3, *np;
struct tailhead head; /* Tail queue head */
int i;
TAILQ_INIT(&head); /* Initialize the queue */
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
TAILQ_INSERT_HEAD(&head, n1, entries);
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
TAILQ_INSERT_TAIL(&head, n1, entries);
n2 = malloc(sizeof(struct entry)); /* Insert after */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);
n3 = malloc(sizeof(struct entry)); /* Insert before */
TAILQ_INSERT_BEFORE(n2, n3, entries);
TAILQ_REMOVE(&head, n2, entries); /* Deletion */
free(n2);
/* Forward traversal */
i = 0;
TAILQ_FOREACH(np, &head, entries)
np->data = i++;
/* Reverse traversal */
TAILQ_FOREACH_REVERSE(np, &head, tailhead, entries)
printf("%i\n", np->data);
/* TailQ deletion */
n1 = TAILQ_FIRST(&head);
while (n1 != NULL) {
n2 = TAILQ_NEXT(n1, entries);
free(n1);
n1 = n2;
}
TAILQ_INIT(&head);
exit(EXIT_SUCCESS);
}
代码分析
TAILQ_ENTRY 和 TAILQ_HEAD
struct entry {
int data;
TAILQ_ENTRY(entry) entries; /* Tail queue */
};
TAILQ_HEAD(tailhead, entry);
TAILQ_ENTRY(entry) entries
中的 entry 由用户指定,是外层大结构体的标签,宏展开后就是下面的第 1,4,5 行中的 entry;entries 也由用户指定,是无标签结构体的成员名,宏展开后是下面第 6 行中的 entries
struct entry {
int data;
struct {
struct entry *tqe_next;
struct entry **tqe_prev;
} entries;
};
struct tailhead {
struct entry *tqh_first;
struct entry **tqh_last;
};
TAILQ_HEAD(tailhead, entry)
中的 tailhead 由用户指定,是表头结构体的标签,如上面第 9 行的 tailhead,entry 要和 TAILQ_ENTRY
中的第一个参数保持一致。
struct entry **tqh_last
和 struct entry **tqe_prev
看起来有点诡异,为什么是二级指针?
改成一级的行不行?
如果把第 5 行改成 struct entry *tqe_prev,肯定不行,如果它的前面是头节点怎么办,头节点的定义里面没有 struct entry 啊!
再仔细看看,不管是头节点还是普通节点,都有 struct entry * 这个类型,那就定义一个类型为 struct entry ** 的变量好了。这样前插的时候就可以统一处理了,例子见 TAILQ_INSERT_BEFORE
总结一下,定义 TAILQ 的时候,有 3 个基本要素:
-
结构体 struct entry:TAILQ_ENTRY() 的参数和 TAILQ_HEAD() 的第二个参数
-
结构体 struct tailhead:TAILQ_HEAD() 的第一个参数
-
变量 entries:紧跟在 TAILQ_ENTRY() 后面
TAILQ_INIT
struct entry *n1, *n2, *n3, *np;
struct tailhead head; /* Tail queue head */
int i;
TAILQ_INIT(&head); /* Initialize the queue */
第 5 行,宏展开是
(((&head))->tqh_first) = ((void *)0);
(&head)->tqh_last = &(((&head))->tqh_first);
意思是表头的 tqh_first 为 NULL,tqh_last 指向自己的 tqh_first
TAILQ_INSERT_HEAD
n1 = malloc(sizeof(struct entry)); /* Insert at the head */
TAILQ_INSERT_HEAD(&head, n1, entries);
2:宏展开
if (((((n1))->entries.tqe_next) = (((&head))->tqh_first)) != ((void *)0))
(((&head))->tqh_first)->entries.tqe_prev = &(((n1))->entries.tqe_next);
else
(&head)->tqh_last = &(((n1))->entries.tqe_next);
(((&head))->tqh_first) = (n1);
(n1)->entries.tqe_prev = &(((&head))->tqh_first);
要把 n1 插入到队列的第一个位置,需要修改哪些指针呢?
- n1 的 entries.tqe_next
- n1 的 entries.tqe_prev
- (&head))->tqh_first
- 之前第一个节点的 entries.tqe_prev
- 如果 n1 是唯一的节点,还需要修改 (&head)->tqh_last
好,我们看代码是如何解决的。
第 1 行解决了 1
第 2 行解决了 4,这个要解释一下,(((&head))->tqh_first)->entries.tqe_prev 表示插入 n1 之前的第一个节点的 tqe_prev,插入后,它应该指向 n1 的 tqe_next
第 4 行解决了 5
第 6 行解决了 3
第 7 行解决了 2
TAILQ_INSERT_TAIL
n1 = malloc(sizeof(struct entry)); /* Insert at the tail */
TAILQ_INSERT_TAIL(&head, n1, entries);
把 n1 插入到队列的末尾,需要修改哪些指针呢?
- (&head)->tqh_last
- n1 的 entries.tqe_next 应该为 NULL
- n1 的 entries.tqe_prev
- n1 插入之前最后一个节点(叫 n0 吧)的 entries.tqe_next
第 2 行,展开宏
(((n1))->entries.tqe_next) = ((void *)0);
(n1)->entries.tqe_prev = (&head)->tqh_last;
*(&head)->tqh_last = (n1);
(&head)->tqh_last = &(((n1))->entries.tqe_next);
第 1 行解决了 2
第 2 行解决了 3
第 3 行解决了 4,因为插入之前 (&head)->tqh_last 指向 n0 的 entries.tqe_next,对 (&head)->tqh_last 解引用,就得到变量 n0 的 entries.tqe_next,它应该等于 n1
第 4 行解决了 1
TAILQ_INSERT_AFTER
n2 = malloc(sizeof(struct entry)); /* Insert after */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);
把 n2 插入到 n1 的后面,需要修改哪些指针呢?
- n1 的 entries.tqe_next
- n2 的 entries.tqe_prev
- n2 的 entries.tqe_next
- 插入 n2 之前,如果 n1 后面有个 n3,则需要修改 n3 的 entries.tqe_prev;
- 插入 n2 之前,如果 n1 是最后一个节点,则要修改 (&head)->tqh_last
第 2 行宏展开:
if (((((n2))->entries.tqe_next) = (((n1))->entries.tqe_next)) != ((void *)0))
(((n2))->entries.tqe_next)->entries.tqe_prev = &(((n2))->entries.tqe_next);
else
(&head)->tqh_last = &(((n2))->entries.tqe_next);
(((n1))->entries.tqe_next) = (n2);
(n2)->entries.tqe_prev = &(((n1))->entries.tqe_next);
第 1 行解决了 3
第 2 行解决了 4
第 4 行解决了 5
第 6 行解决了1
第 7 行解决了 2
TAILQ_INSERT_BEFORE
n3 = malloc(sizeof(struct entry)); /* Insert before */
TAILQ_INSERT_BEFORE(n2, n3, entries);
把 n3 插入到 n2 的前面,需要修改哪些指针呢?
-
n3 的 entries.tqe_next
-
n2 的 entries.tqe_prev
-
n3 的 entries.tqe_prev
-
插入 n3 之前,如果 n2 的前面有个 n1,则需要修改 n1 的 entries.tqe_next,
即
(n1)->entries.tqe_next = n3;
-
插入 n3 之前,如果 n2 是第一个节点,则要修改 (&head)->tqh_first,
即
(&head)->tqh_first = n3;
第 2 行宏展开
(n3)->entries.tqe_prev = (n2)->entries.tqe_prev;
(((n3))->entries.tqe_next) = (n2);
*(n2)->entries.tqe_prev = (n3);
(n2)->entries.tqe_prev = &(((n3))->entries.tqe_next);
第 1 行解决了 3
第 2 行解决了 1
第 3 行解决了 4 和 5(马上会解释)
第 4 行解决了 2
解释:
插入 n3 之前,如果 n2 的前面有个 n1,
那么 *(n2)->entries.tqe_prev
代表变量 (n1)->entries.tqe_next
插入 n3 之前,如果 n2 是第一个节点,
那么 *(n2)->entries.tqe_prev
代表变量 (&head)->tqh_first
所以这两种情况可以合并,写成
*(n2)->entries.tqe_prev = (n3);
TAILQ_REMOVE
TAILQ_REMOVE(&head, n2, entries); /* Deletion */
free(n2);
要把 n2 从链表移除,需要修改哪些指针呢?
- n2 前面那个节点(假设是 n1)的 entries.tqe_next
- n2 后面那个节点(假设是 n3)的 entries.tqe_prev
- 如果 n2 是最后一个节点,那么要修改 (&head)->tqh_last
第 1 行宏展开
if (((((n2))->entries.tqe_next)) != ((void *)0))
(((n2))->entries.tqe_next)->entries.tqe_prev = (n2)->entries.tqe_prev;
else
(&head)->tqh_last = (n2)->entries.tqe_prev;
*(n2)->entries.tqe_prev = (((n2))->entries.tqe_next);
第 1 行判断 n2 是否为最后一个节点
第 2 行解决了 2
第 4 行解决了 3
第 5 行解决了 1,这个解释一下。(n2)->entries.tqe_prev 指向的是 n1 的 entries.tqe_next,对 (n2)->entries.tqe_prev 解引用,得到变量 (n1)->entries.tqe_next,需要给它赋值为 n3 的地址,即 (n2)->entries.tqe_next;如果 (n2)->entries.tqe_prev 指向表头的 tqh_first,第 5 行也是成立的
TAILQ_FOREACH
i = 0;/* Forward traversal */
TAILQ_FOREACH(np, &head, entries)
np->data = i++;
第 2 行就是
for ((np) = (((&head))->tqh_first); (np); (np) = (((np))->entries.tqe_next))
比较简单,不需要解释。
TAILQ_FOREACH_REVERSE
/* Reverse traversal */
TAILQ_FOREACH_REVERSE(np, &head, tailhead, entries)
printf("%i\n", np->data);
这次我们换个思路,不看展开后是什么,而是看看宏的定义
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
第 7 行,可以得到最后一个节点的地址。解释一下:
(head)->tqh_last 是最后一个节点的 entries.tqe_next 的地址,可是这个地址不是用户想要的,用户要的是节点的地址,就是大结构体( struct entry )的地址,所以只好曲线救国,如下图,从 1 到 2 再到 3
从 entries.tqe_next 的地址怎么得到 entries.tqe_prev 的值呢?
其实也不难,虽然 struct entry 里面 entries 这个结构体变量没有标签,但是它的元素构成和 struct headname 完全一样,都是一个 struct entry * 和 一个 struct entry **,所以可以把 entries.tqe_next 的地址强制转换为 (struct headname *) 类型: (struct headname *)((head)->tqh_last)
然后访问它的 tqh_last 成员(也就是图中的 prev):
((struct headname *)((head)->tqh_last))->tqh_last
这样就得到了前一个节点的 entries.tqe_next 的地址,再对它解引用:
*(((struct headname *)((head)->tqh_last))->tqh_last)
就得到了 entries.tqe_next 变量,这恰好是大结构体( struct entry )的地址
我们再看 TAILQ_PREV 这个宏,道理类似。
如图所示,从 1 到 2 再到 3
设当前节点的地址是 elm(图中用最后这个节点来示意),(elm)->field.tqe_prev
,表示顺着 1,得到它前面那个节点的 next 的地址,对这个地址强制转换为 struct headname *,即
(struct headname *)((elm)->field.tqe_prev)
再访问 prev,也就是顺着 2,得到了 elm 前面的前面的节点的 next 地址
即 ((struct headname *)((elm)->field.tqe_prev))->tqh_last
对它解引用,就得到 elm 前面的前面的节点的 next,即
*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)
它刚好是 3 这个指针表示的地址。
其他的宏比较简单,就不介绍了
参考资料
https://manpages.courier-mta.org/htmlman3/tailq.3.html