include/qemu/queue.h
中的注释如下。该文件通过宏定义的方式,定义了4种数据结构,以及相关的操作。
四种数据结构分别是:singly-linked list
单向链表、list
双向链表、simple queue
简单队列、tail queue
尾队列。
1/*2 * This file defines four types of data structures: singly-linked lists,3 * lists, simple queues, and tail queues.4 *5 * A singly-linked list is headed by a single forward pointer. The6 * elements are singly linked for minimum space and pointer manipulation7 * overhead at the expense of O(n) removal for arbitrary elements. New8 * elements can be added to the list after an existing element or at the9 * head of the list. Elements being removed from the head of the list10 * should use the explicit macro for this purpose for optimum11 * efficiency. A singly-linked list may only be traversed in the forward12 * direction. Singly-linked lists are ideal for applications with large13 * datasets and few or no removals or for implementing a LIFO queue.14 *15 * A list is headed by a single forward pointer (or an array of forward22 collapsed lines
16 * pointers for a hash table header). The elements are doubly linked17 * so that an arbitrary element can be removed without a need to18 * traverse the list. New elements can be added to the list before19 * or after an existing element or at the head of the list. A list20 * may only be traversed in the forward direction.21 *22 * A simple queue is headed by a pair of pointers, one the head of the23 * list and the other to the tail of the list. The elements are singly24 * linked to save space, so elements can only be removed from the25 * head of the list. New elements can be added to the list after26 * an existing element, at the head of the list, or at the end of the27 * list. A simple queue may only be traversed in the forward direction.28 *29 * A tail queue is headed by a pair of pointers, one to the head of the30 * list and the other to the tail of the list. The elements are doubly31 * linked so that an arbitrary element can be removed without a need to32 * traverse the list. New elements can be added to the list before or33 * after an existing element, at the head of the list, or at the end of34 * the list. A tail queue may be traversed in either direction.35 *36 * For details on the use of these macros, see the queue(3) manual page.37 */
本文主要描述tail queue
,并简单介绍其他三种数据结构。
singly-linked list
单向链表
特点:
- 由元素组成,每个元素通过单个向前的指针连接。
- 在列表头部或某个元素之后添加元素效率较高。
- 移除元素通常需要O(n)时间,因为可能涉及遍历列表以找到元素。
- 在删除操作较少或数据以后进先出(LIFO)方式增长的场景中最为理想。
适用场景:
- 在数据集大且删除操作少的应用中非常有用。
- 适合堆栈实现,其中元素不断地被推入并从头部弹出。
list
双向链表
特点:
- 元素是双向链接的,允许轻松移除任何元素而无需完全遍历。
- 只能向前遍历。
- 新元素可以在列表的头部或现有元素之前或之后添加。
适用场景:
- 适合频繁插入和从列表任何部分删除的应用。
- 在需要快速访问和修改数据且不希望遍历列表带来性能损失的场景中常用。
simple queue
简单队列
特点:
- 由两个指针头部,一个指向列表的头部,另一个指向尾部。
- 元素单向链接,这意味着节省空间,但只允许从头部移除。
- 支持在列表的末尾或现有元素之后添加元素。
适用场景:
- 适合队列实现,其中元素在尾部入队,在头部出队(先进先出,FIFO)。
- 在主要是追加操作或元素按到达顺序处理的情况下非常有用。
tail queue
尾队列
特点:
- 类似于简单队列,但元素是双向链接的。
- 允许移除任何元素而无需遍历整个列表。
- 元素可以在列表的头部或尾部添加或移除,列表可以向任一方向遍历。
适用场景:
- 在需要频繁在列表两端插入和删除的情况下非常灵活。
- 理想用于实现双端队列(deque),在两端高效添加或删除数据。
在QEMU中tail queue
被大量使用。
tail queue
数据结构形式
QTailQLink
文件include/qemu/queue.h
中定义了tail queue
最重要的数据结构QTailQLink
,这是一个内部结构,被QTAILQ_HEAD
和QTAILQ_ENTRY
使用。主要目的在于尽可能不影响自定义数据结构的同时,将数据结构联系起来。
1typedef struct QTailQLink {2 void *tql_next;3 struct QTailQLink *tql_prev;4} QTailQLink;
QTailQLink
定义的tql_next
类型为void *
,其作用是指向当前节点后续的自定义数据结构节点,由于自定义数据结构类型不能确定,因此只能用void *
定义。tql_prev
则是指向当前节点的前一个QTailQLink
节点。这两个变量的在后续QTAILQ_HEAD
和QTAILQ_ENTRY
中将体现出独特的作用。
QTAILQ_HEAD
QTAILQ_HEAD
定义如下:
1/*2 * Tail queue definitions. The union acts as a poor man template, as if3 * it were QTailQLink<type>.4 */5#define QTAILQ_HEAD(name, type) \6union name { \7 struct type *tqh_first; /* first element */ \8 QTailQLink tqh_circ; /* link for circular backwards list */ \9}10/*11 * union使两个变量共享同一个空间:tqh_first为一个指针,QTailQLink包含两个指针。12 * 因此,tqh_first和tqh_circ.tql_next储存在同一个位置。13 * 即tqh_circ.tql_next就是tqh_first。14 */
需要注意,QTAILQ_HEAD
被定义为union
结构体,也就是union
结构体中所有的变量共享同一块内存空间。当我们QTailQLink
展开后,tqh_first
和QTailQLink.tql_next
共享同一块内存,也就是两者实际上的值是相同的,只不过名字不同。
QTAILQ_ENTRY
同理,QTAILQ_ENTRY
也是如此:
1#define QTAILQ_ENTRY(type) \2union { \3 struct type *tqe_next; /* next element */ \4 QTailQLink tqe_circ; /* link for circular backwards list */ \5}
QTAILQ_HEAD
和QTAILQ_ENTRY
区别在与,QTAILQ_HEAD.tqh_first
指向第一个自定义数据结构体,QTAILQ_HEAD
本身是一个单独节点,不会存在于自定义数据结构中。而QTAILQ_ENTRY
是定义在自定义数据结构体中的,作为自定义数据结构体的一部分。
ModuleEntry
举例
为了直观的理解,以util/module.c
中定义的ModuleEntry
为例:
1// A general element for tail queue, must contain QTAILQ_ENTRY, and the name for2// QTAILQ_ENTRY is arbitrary3// Example4typedef struct ModuleEntry5{6 void (*init)(void); // data only for this struct7 QTAILQ_ENTRY(ModuleEntry) node;// use for queue8 module_init_type type; // data only for this struct9} ModuleEntry;10
11typedef QTAILQ_HEAD(, ModuleEntry) ModuleTypeList;
ModuleEntry
就是自定义数据结构体,其中包含QTAILQ_ENTRY
定义的同时,还包含有其他变量,将其展开后代码如下:
1typedef struct ModuleEntry2{3 void (*init)(void);4 union {5 struct ModuleEntry *tqe_next;6 struct QTailQLink {7 void *tql_next;8 struct QTailQLink *tql_prev;9 } tqe_circ;10 } node;11 module_init_type type;12} ModuleEntry;
根据在QTAILQ_ENTRY
中的对union
结构体的分析,我们可以得到
1ModuleEntry entry;2/* 下面两者等价,指向ModuleEntry类型 */3(ModuleEntry *) entry.node.tqe_next4(ModuleEntry *) entry.node.tqe_circ.tql_next5
6/* 指向struct QTailQLink */7(struct QTailQLink *) entry.node.tqe_circ.tql_prev
而QTAILQ_HEAD
则是单独定义的:
1typedef QTAILQ_HEAD(, ModuleEntry) ModuleTypeList;2static ModuleTypeList dso_init_list;
同样将其展开,可以看到和ModuleEntry
不同点在在于,其中不包含其他类型的数据。
1typedef union ModuleTypeList2{3 struct ModuleEntry *tqh_first;4 struct QTailQLink {5 void *tql_next;6 struct QTailQLink *tql_prev;7 } tqh_circ;8} ModuleTypeList;
在实际使用时,如下图组织数据:ModuleTypeList
定义头节点,ModuleEntry
定义中间节点,双向队列由头节点和 node 之间的指针来维护。
需要注意的是,所有next指针(tql_next
、tqe_next
)指向的都是自定义数据结构,而所有的prev指针(tql_prev
)指向的都是struct QTailQLink
(即tqh_circ
,tqe_circ
)。这意味着,如果我们想要访问cur节点的前一个节点,需要从tql_prev->tql_prev->tql_next
来访问。
其他API
head初始化
有两种方式实现:QTAILQ_HEAD_INITIALIZER
和QTAILQ_INIT
。
QTAILQ_HEAD_INITIALIZER
实际上是宏定义的结构体赋值文本,不算是函数接口(api)。
QTAILQ_INIT
是宏定义的函数体,算是函数接口(api)。
因此,函数调用时使用QTAILQ_INIT
,而定义struct时需要初始化head,则使用QTAILQ_HEAD_INITIALIZER
。
1/* */2#define QTAILQ_HEAD_INITIALIZER(head) \3 { .tqh_circ = { NULL, &(head).tqh_circ } }4/*5 * HEAD初始化时,头尾是同一个节点(本HEAD节点)6 * 和后续的 QTAILQ_INIT(head)作用相同,但用途不同。7 * QTAILQ_HEAD_INITIALIZER 是宏定义,用于自定义struct初始化head时使用。8 * QTAILQ_INIT 是函数,在函数中调用实现head初始化。9 */10
11/* QTAILQ_HEAD_INITIALIZER */12static QemuOptsList qemu_source_opts = {13 .name = "source",14 .implied_opt_name = "file",15 .head = QTAILQ_HEAD_INITIALIZER(qemu_source_opts.head),35 collapsed lines
16 .desc = {17 { }18 },19};20
21/*22 * Tail queue functions.23 */24#define QTAILQ_INIT(head) do { \25 (head)->tqh_first = NULL; \26 (head)->tqh_circ.tql_prev = &(head)->tqh_circ; \27} while (/*CONSTCOND*/0)28// 初始化队列时,由于只有一个HEAD节点,因此HEAD的prev指向自身,next(即first元素)指向空29// 否则HEAD的prev指向的就是尾元素30
31/* QTAILQ_INIT */32static ModuleTypeList init_type_list[MODULE_INIT_MAX];33static ModuleTypeList dso_init_list;34static void init_lists(void)35{36 static int inited;37 int i;38
39 if (inited) {40 return;41 }42
43 for (i = 0; i < MODULE_INIT_MAX; i++) {44 QTAILQ_INIT(&init_type_list[i]);45 }46
47 QTAILQ_INIT(&dso_init_list);48
49 inited = 1;50}
头部和尾部插入
由于是宏定义实现的接口,我们预先不知道自定义的数据结构的名称,以及其中的strutc QTailQLink
名称,因此需要传入这两个参数,elm
和field
。例如,ModuleEntry
对应于elm
,ModuleEntry
中的node
对应于field
。
1/*2 * Tail queue functions.3 */4#define QTAILQ_INSERT_HEAD(head, elm, field) do { \5 if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \6 (head)->tqh_first->field.tqe_circ.tql_prev = \7 &(elm)->field.tqe_circ; \8 else \9 (head)->tqh_circ.tql_prev = &(elm)->field.tqe_circ; \10 (head)->tqh_first = (elm); \11 (elm)->field.tqe_circ.tql_prev = &(head)->tqh_circ; \12} while (/*CONSTCOND*/0)13// 向头节点插入时,先判断HEAD后有没有其他元素,并将当前元素elm的next指针指向HEAD后的元素14// 如果有其他元素,则将HEAD的后续元素的prev指针指向当前元素elm15// 如果没有其他元素,则将HEAD的prev指针指向当前元素elm。49 collapsed lines
16// 然后将HEAD的first指向elm,因为first其实就是HEAD的next17// 最后将elm的prev指向head。18
19// 需要注意:HEAD中的tqe_circ、elm中的field(即QTAILQ_ENTRY)的tqe_circ20// 才是整个链条中的“节点”,21// elm中,field就是QTAILQ_ENTRY,用来标识节点,因此elm不仅有“节点”,还有其他的数据22// 而在HEAD中,HEAD就是QTAILQ_HEAD,和QTAILQ_ENTRY同属基本的queue数据结构,23// 因此,在整个链条中,QTAILQ_HEAD和QTAILQ_ENTRY作为队列中的节点,其中的指针24// prev和next链接每个节点。25
26// 在实现中很有意思的一点,next指针的类型是type *,而pre的指针类型是QTailQLink *27// 因此,在访问next中的“节点”时,需要访问next.field.tqe_circ,而pre直接就访问28// pre.tqe_circ29
30#define QTAILQ_INSERT_TAIL(head, elm, field) do { \31 (elm)->field.tqe_next = NULL; \32 (elm)->field.tqe_circ.tql_prev = (head)->tqh_circ.tql_prev; \33 (head)->tqh_circ.tql_prev->tql_next = (elm); \34 (head)->tqh_circ.tql_prev = &(elm)->field.tqe_circ; \35} while (/*CONSTCOND*/0)36
37#define QTAILQ_INSERT_AFTER(head, listelm, elm, field) do { \38 if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\39 (elm)->field.tqe_next->field.tqe_circ.tql_prev = \40 &(elm)->field.tqe_circ; \41 else \42 (head)->tqh_circ.tql_prev = &(elm)->field.tqe_circ; \43 (listelm)->field.tqe_next = (elm); \44 (elm)->field.tqe_circ.tql_prev = &(listelm)->field.tqe_circ; \45} while (/*CONSTCOND*/0)46
47#define QTAILQ_INSERT_BEFORE(listelm, elm, field) do { \48 (elm)->field.tqe_circ.tql_prev = (listelm)->field.tqe_circ.tql_prev; \49 (elm)->field.tqe_next = (listelm); \50 (listelm)->field.tqe_circ.tql_prev->tql_next = (elm); \51 (listelm)->field.tqe_circ.tql_prev = &(elm)->field.tqe_circ; \52} while (/*CONSTCOND*/0)53
54#define QTAILQ_REMOVE(head, elm, field) do { \55 if (((elm)->field.tqe_next) != NULL) \56 (elm)->field.tqe_next->field.tqe_circ.tql_prev = \57 (elm)->field.tqe_circ.tql_prev; \58 else \59 (head)->tqh_circ.tql_prev = (elm)->field.tqe_circ.tql_prev; \60 (elm)->field.tqe_circ.tql_prev->tql_next = (elm)->field.tqe_next; \61 (elm)->field.tqe_circ.tql_prev = NULL; \62 (elm)->field.tqe_circ.tql_next = NULL; \63 (elm)->field.tqe_next = NULL; \64} while (/*CONSTCOND*/0)
除了上述Tail queue functions
,文件中还定义了一些访问方法,不在此处展示,当明白数据组织后,再去看源码将很容易。