这几天在阅读qemu内存模型相关代码,发现在MemoryRegion结构体中使用了QTAIL_HEAD和QTAIL_ENTRY两个宏定义进行队列(链表)定义,然后使用QTAILQ_FOREACH,QTAILQ_INSERT_BEFORE,QTAILQ_INSERT_TAIL进行队列(链表)操作。其实在qemu源代码中好多处都用到了以上相关宏定义,今天花时间研究了一波这几个相关的宏定义,简单记录一下。
先撇开qemu相关知识,就仅仅对以上几个宏定义进行分析
TAILQ相关的宏定义主要有如下几个:
TAILQ_HEAD 定义队列头
TAILQ_ENTRY 队列实体定义
TAILQ_INIT 初始化队列
TAILQ_FOREACH 对队列进行遍历操作
TAILQ_INSERT_BEFORE 在指定元素之前插入元素
TAILQ_INSERT_TAIL 在队列尾部插入元素
TAILQ_EMPTY 检查队列是否为空
TAILQ_REMOVE 从队列中移除元素
为了更好理解,通过直接用以上宏创建一个链表队列来对上述宏进行讲解。
(1)为了实现一个链表队列,首先我们得定义一个结构体作为链表队列结点,结构体定义如下:
typedef struct Node {
/*some attr*/
int id;
/*link*/
TAILQ_ENTRY(Node) link;
} Node;
上面结构体定义中我们使用了 TAILQ_ENTRY 来定义结点间的链接,TAILQ_ENTRY宏定义如下:
#define TAILQ_ENTRY(type) \
struct { \
type *tqe_next; /* next element */ \
type **tqe_prev; /* address of previous next element */ \
}
将TAILQ_ENTRY宏定义在我们定义的Node结构体中展开如下:
typedef struct Node {
/*some attr*/
int id;
/*link*/
struct {
Node *tqe_next;
Node **tqe_prev;
} link;
} Node;
在link属性中包含了两个元素,tqe_next和tqe_prev,其中tqe_next指向下一个Node结点,但是tqe_pre却不是指向前一个node结点(刚开始看这些代码的时候一直以为tqe_prev是指向前一个结点的,最终分析代码始终感觉怪怪的才发现tqe_pre不是指向前一个结点,其实早就应该知道tqe_pre是一个二级指针,肯定有其他用途,不应认为指向前面一个结点,真实太蠢了),tqe_pre是指向前一个结点的link.tqe_next属性,所以它才会是一个二级指针的形式。直接上图,假设我们已经使用上述结构体创建了一个队列,该队列的指针指向如下图所示:
(2)创建好链表队列结点结构体后,我们需要创建一个链表队列头方便我们对队列进行操作,创建代码如下:
TAILQ_HEAD(head, Node) head;
创建链表头使用的宏定义如下:
#define TAILQ_HEAD(name, type) \
struct name { \
type * tqh_first; /* first element */ \
type ** tqh_last; /* addr of last next element */ \
}
宏定义展开后代码如下:
struct head {
Node * tqh_first;
Node ** tqh_last;
} head;
该头也有两属性,第一个tqh_first指向队列的第一个元素,第二个属性也是一个二级指针,虽然命名为last,但是不是指向链表队列最后一个元素,而是指向链表队列最后一个元素的link.next属性,如下图所示:
(3)准备好队列头的结点结构体后,可以开始初始化一个链表队列的,初始化链表队列代码如下:
TAILQ_INIT(head);
队列初始化宏定义如下:
#define TAILQ_INIT(head) do { \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (/*CONSTCOND*/0)
以上代码比较简单,就是对队列头的两个属性赋初始值。
(4)在链表队列中插入元素,既可以在链表队列的头部插入元素,也可以在链表队列的尾部插入元素,还可以在链表队列的中间插入元素(变异的队列。。。),接下来对插入元素的几种方式分别介绍。
(I)在链表队列尾部插入元素代码如下,动态分配一个Node结点node01,并将其插入到队列尾部
Node * node01 = (Node *)malloc(sizeof(Node));
TAILQ_INSERT_TAIL(head, node01, link);
所使用的TAILQ_INSERT_TAIL定义如下:
#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)
将在链表队列尾部插入元素的代码展开如下:
do {
(node01)->link.tqe_next = ((void *)0);
(node01)->link.tqe_prev = (head)->tqh_last;
*(head)->tqh_last = (node01);
(head)->tqh_last = &(node01)->link.tqe_next;
} while ( 0)
插入元素宏定义展开后总共4行代码,下图图示了这4行代码实现流程:
假设队列中已经有3个结点,我们需要在尾部插入第4个结点
第1行代码将第四个结点的tqe_next设置为(void *)0,可以直接理解为置为NULL。
第2行代码中将tqe_pre指向前一个结点的tqe_next属性,因为head中的tqh_last指向了链表最后一个元素的tqe_next属性,所以这一步只需要将head->tqh_last的值直接赋给tqe_pre即可。
第3行代码实现将原队列最后一个元素tqe_next指向新增的node结点。
第4行代码就是将tqh_last指向新增结点的tqe_next属性。
通过上述4行代码我们便完成了在队列尾部新增结点的操作。通过上述操作我们可以发现,在链表尾部新增结点只需要4步,并不会因为链表节点数变多而导致时间复杂度增大,这就是head节点的意义,通过一个二级指针tql_last指向最后一个元素的tqe_next, 使得在队列尾部插入元素的时间复杂度为O(1)。
(II) 在链表队列头部插入元素所使用的宏如下,只要理解了上图中的指针指向,下面这个宏分析起来应该也容易,这边就不做进一步分析。
#define QTAILQ_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)
(III)在链表队列中间插入元素使用的宏如下:
#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 (/*CONSTCOND*/0)
如下图已经有3个结点了,我们需要将一个新的结点插入到倒数第二的位置
前2行代码将新增结点的tqe_next指向了原队列第2个结点的tqe_next属性,将新增结点的tqe_next属性指向被插入结点。
第3行代码将被插入位置的前一个结点的tqe_next指向新增结点,第4行代码将被插入结点的tqe_pre指向新增结点的tqe_next属性。
(5)遍历队列使用的宏如下:
#define TAILQ_FOREACH(var, head, field) \
for ((var) = ((head)->tqh_first); \
(var); \
(var) = ((var)->field.tqe_next))
(6)删除队列元素的宏如下:
#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; \
(elm)->field.tqe_prev = NULL; \
} while (/*CONSTCOND*/0)