标签:struct 过多 lin 它的 地址 linked backend 合并 block
qemu数据结构分析
这里主要分析queue.h头文件中包含的四种数据结构:1.单链表 2.双链表 3.简单队列 4.尾队列
一、单链表
1.1 应用场景
适用于数据量大的集合并且没有过多删除动作的场景,也适用做LIFO(后进先出)队列。
1.2 定义
/*
struct name {
struct type slh_first; / first element */
}
{ NULL }
struct {
struct type sle_next; / next element */
}
1.3 元素插入
(elm)->field.sle_next = (slistelm)->field.sle_next; (slistelm)->field.sle_next = (elm);
} while (/CONSTCOND/0)
qemu代码中并没有使用该数据结构,这里简单分析一下该链表的元素插入方式。
插入流程比较简单,slistelm是待插入元素的前一个元素位置,elm是待插入元素,field是数据结构中的域名称,这里是next。
插入过程:1.将elm元素的next域的sle_next指向slistelm指向的下一个元素
2.将slistelm指向待插入元素elm
二、双向链表
2.1 应用场景
由于有前向指针,因此删除元素不需要遍历链表。新加入元素可以实现前插或后插。
2.2 定义
/*
struct name {
struct type lh_first; / first element */
}
{ NULL }
struct {
struct type le_next; / next element */
struct type *le_prev; / address of previous next element */
}
2.3 插入操作分析
if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) (listelm)->field.le_next->field.le_prev = &(elm)->field.le_next; (listelm)->field.le_next = (elm); (elm)->field.le_prev = &(listelm)->field.le_next;
} while (/CONSTCOND/0)
插入过程:1. 首先待插入元素的le_next指针指向插入位置的下一个元素
待插入位置的下一个元素的le_prev指向待插入元素的le_next指针。注意这里的le_prev是一个指向指针的指针,因此le_prev保存的是指针le_next的地址,而不是它的值。如果是值的话就变成指向自身了。
前一个元素的le_next开始指向待插入元素
最后待插入元素的le_prev指向其前一个元素的le_next指针。
2.4 在qemu中的应用
双向链表在qemu中有大量使用,如block设备链(block-backend.c)。
三、简单队列
3.1 定义
/*
struct name {
struct type sqh_first; / first element */
struct type *sqh_last; / addr of last next element */
}
{ NULL, &(head).sqh_first }
struct {
struct type sqe_next; / next element */
}
2.3 元素插入
if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL) (head)->sqh_last = &(elm)->field.sqe_next; (listelm)->field.sqe_next = (elm);
} while (/CONSTCOND/0
简单队列的插入过程也比较简单
插入过程:1. 首先将待插入元素的sqe_next指针指向待插入位置的下一个元素
四、尾队列
4.1 应用场景
尾队列在链表头有一个头指针,在链表尾有一个尾指针。元素由前向指针和尾指针构成双向链表。可以在任意方向上进行遍历。
4.2 定义
/*
union name {
struct type tqh_first; / first element /
QTailQLink tqh_circ; / link for circular backwards list */
}
{ .tqh_circ = { NULL, &(head).tqh_circ } }
union {
struct type tqe_next; / next element /
QTailQLink tqe_circ; / link for circular backwards list */
}
4.3 插入过程
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL) (elm)->field.tqe_next->field.tqe_circ.tql_prev = &(elm)->field.tqe_circ; else (head)->tqh_circ.tql_prev = &(elm)->field.tqe_circ; (listelm)->field.tqe_next = (elm); (elm)->field.tqe_circ.tql_prev = &(listelm)->field.tqe_circ;
} while (/CONSTCOND/0)
尾队列结构由于头结点有一个tqh_circ指针始终指向尾结点,因此可以以O(1)的复杂度在尾部插入元素。同时能够以O(1)的复杂度在头部删除元素。
尾队列结构在中间的插入过程比较复杂。
插入过程:1.将待插入元素elm的tqe_next指针指向插入位置的下一个元素
将插入位置下一个元素的tql_prev指针指向待插入元素的tqe_circ。这里和双向链表一样,tql_prev是一个指向指针的指针,保存的是前一个节点中tqe_circ指针的地址。
第一,二步实际上完成了插入元素与插入位置下一个元素之间的连接,构成了一个循环。现在开始的是与插入位置前一个元素之间的连接。首先将插入位置前一个元素的tqe_next指向待插入元素elm。
最后将待插入元素的tql_prev指向前一个元素。保存前一个元素tqe_circ指针的地址。
4.4 在qem中的应用
尾队列的应用十分广泛,在内核,libevent中都能看到它的身影。主要得益于它O(1)复杂度的插入和删除操作以及可以双向遍历。
在qemu中,如memory_mapping使用的就是QTAIL_LIST(memory_mapping.c)。
五、总结
在qemu中主要使用双向链表和尾队列这两种数据结构。对于这两种数据结构的初始化,插入,添加,删除操作都使用宏定义做了封装。理解了这两种数据结构,可以更好的理解qemu相关模块的代码。
标签:struct 过多 lin 它的 地址 linked backend 合并 block
原文地址:https://www.cnblogs.com/foxmai/p/13892334.html