查看suricata源码时,看到有使用Tail queue作为内存存储配置文件解析内容的结构,这里对Tail queue这个结构及其操作方式做部分记录。

这是一个使用的例子,重点为结构体中的head和next成员。一个ConfNode类型节点有子节点时,使用head成员作为头,连接各个子节点的next成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct ConfNode_ {
char *name;
char *val;

int is_seq;

/**< Flag that sets this nodes value as final. */
int final;

struct ConfNode_ *parent;
TAILQ_HEAD(, ConfNode_) head;
TAILQ_ENTRY(ConfNode_) next;
} ConfNode;
队列数据结构。name为头结点的结构体名,可为空。type为实际的数据节点结构体名,这个结构体将包含队列entry结构。
1
2
3
4
5
6
7
8
9
10
11
12
#define TAILQ_HEAD(name, type)                      \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}


#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
队列头初始化代码
1
2
3
4
#define TAILQ_INIT(head) do {                       \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (0)

初始化后结构

队列操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#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 (0)

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

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

#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
(elm)->field.tqe_next = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
} while (0)

#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; \
_Q_ASSERT((head)->tqh_first != (elm)); \
_Q_INVALIDATE((elm)->field.tqe_prev); \
_Q_INVALIDATE((elm)->field.tqe_next); \
} while (0)

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

插入数据节点后结构

队列节点访问操作
1
2
3
4
5
6
7
8
9
10
#define TAILQ_FIRST(head)       ((head)->tqh_first)
#define TAILQ_END(head) NULL
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
/* XXX */
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head) \
(TAILQ_FIRST(head) == TAILQ_END(head))

前三个操作比较容易理解,head为头结点指针,elm为entry所在的结构体指针,field为entry在结构体中的名字。

第四个操作TAILQ_LAST需要结合内存布局理解:

  • head为头节点指针,headname为上文定义头节点时的结构体名,如果需要使用TAILQ_LAST操作就必须在定义时设定name。
  • head节点中定义tqh_first,tqh_last有顺序要求。
  • entry节点中定义tqe_next,tqe_prev有顺序要求。
  • ((head)->tqh_last)
    • 是一个二级指针,指向最后一个数据节点的tqe_next,既这个二级指针存储了tqe_next的地址,从内存布局可知这个地址也就是entry节点的地址,这个指针也可以看作是指向entry节点的指针。
  • ((struct headname *)((head)->tqh_last))
    • 将指针做了一次强制类型转换,转换为指向头节点的指针类型,这里仅仅转换了类型,值未发生变化。
  • (((struct headname *)((head)->tqh_last))->tqh_last)
    • 由于头节点和entry节点内存布局相同,这步操作实际相当于得到了最后一个数据节点的tqe_prev。
  • (*(((struct headname *)((head)->tqh_last))->tqh_last))
    • 解引用,由于最后一个数据节点的tqe_prev中存储的是倒数第二个数据节点的tqe_next的地址,解引用后得到了倒数第二个数据节点tqe_next的值,也就是最后一个数据节点的地址,也就是最后一个数据节点的指针。

第五个操作TAILQ_PREV同理。

队列遍历操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define TAILQ_FOREACH(var, head, field)                 \
for((var) = TAILQ_FIRST(head); \
(var) != TAILQ_END(head); \
(var) = TAILQ_NEXT(var, field))

/* removal safe iterator using a temprary element has last param */
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for((var) = TAILQ_FIRST(head), \
(tvar) = TAILQ_FIRST(head) ? TAILQ_NEXT(TAILQ_FIRST(head), field): NULL ; \
(var) != TAILQ_END(head); \
(var = tvar), (tvar) = var ? TAILQ_NEXT(var, field): NULL)

#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for((var) = TAILQ_LAST(head, headname); \
(var) != TAILQ_END(head); \
(var) = TAILQ_PREV(var, headname, field))