qemu设备模型
前言
Qemu支持种类繁多的外部设备,并且支持多种架构,这些架构和设备的模拟在Qemu的代码中占了大头。
这里简单介绍一下Qemu中用于设备模拟的模型,主要分为总线、设备前端和设备后端。
总线
实际上,PC中各组件是通过总线互联通信的。再具体的说,设备与总线是交替的,总线下面只能连接设备,设备也只能连接到总线上,总线与总线、设备与设备之间是不能直接连接的,如下图所示。
参考之前的qemu基本知识中对象初始化内容,根据总线的TypeInfo,即bus_info,即可了解总线对象的相关信息。1
2
3
4
5
6
7
8
9
10
11
12
13
14static const TypeInfo bus_info = {
.name = TYPE_BUS,
.parent = TYPE_OBJECT,
.instance_size = sizeof(BusState),
.abstract = true,
.class_size = sizeof(BusClass),
.instance_init = qbus_initfn,
.instance_finalize = qbus_finalize,
.class_init = bus_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_RESETTABLE_INTERFACE },
{ }
},
};
可以看到,Qemu使用struct BusClass和struct BusState来模拟PC中的总线。
数据结构
struct BusClass
1 | struct BusClass { |
struct BusClass是总线的类结构体,其中重要的是实例化时的realize回调函数和销毁时的unrealize回调函数。
struct BusState
1 | /** |
struct BusState是总线的对象结构体。
parent字段指向的是总线的父设备。正如前面介绍的,总线和设备是交替的,而且总线不能独立产生,必须依赖于一个设备,例如USB总线是由USB控制器产生的,PCI总线是由PCI桥产生的。
children指向当前总线下的所有设备链表,而sibling则指向该总线父设备下的其他总线。
初始化
根据前面内容可知,总线对象使用bus_class_init()初始化类,使用qbus_initfn()初始化对象。
类初始化
1 | static void bus_class_init(ObjectClass *class, void *data) |
总线的类初始化很简单,只是初始化了几个函数指针和接口。
对象初始化
1 | static void qbus_initfn(Object *obj) |
总线的对象初始化也很简单,添加了相关的属性并初始化children字段。
实例化
Qemu中对象初始化仅仅是指初始化好了必要的数据结构信息,还无法直接使用。例如PCI设备只有根据PCI协议完成交互后才能使用,则PCI设备对象则在初始化完后还需要进行协议交互,然后才能正常进行后续的模拟功能。因此对象初始化结束后还需要进行实例化。
根据总线对象初始化的内容,在对象初始化时设置了对象实例化的函数,即bus_set_realized
1 | //#0 bus_set_realized (obj=0x5555573ffd70, value=true, errp=0x7fffffffd4d0) at ../../qemu/hw/core/bus.c:190 |
其逻辑也很简单,在实例化时即调用类的realize函数;而在销毁时递归调用总线上设备的unrealize函数然后再调用类的unrealize函数即可
前端
Qemu在设备模拟上采用了前端和后端分离的设计模式。
具体的,设备前端指的是Qemu模拟设备如何呈现给Guest,呈现的设备类型应该与Guest预期看到的硬件相匹配。设备后端指的是Qemu如何处理来自设备前端的数据。
其中,Qemu设备前端都可以通过-device
命令行进行设置,涉及到Qemu的struct DeviceClass和struct DeviceState结构。
其TypeInfo为device_type_info,如下所示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static const TypeInfo device_type_info = {
.name = TYPE_DEVICE,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DeviceState),
.instance_init = device_initfn,
.instance_post_init = device_post_init,
.instance_finalize = device_finalize,
.class_base_init = device_class_base_init,
.class_init = device_class_init,
.abstract = true,
.class_size = sizeof(DeviceClass),
.interfaces = (InterfaceInfo[]) {
{ TYPE_VMSTATE_IF },
{ TYPE_RESETTABLE_INTERFACE },
{ }
}
};
数据结构
struct DeviceClass
1 | /** |
其中比较重要的是reset、realize和unrealize字段,即重置函数、实例化函数和销毁函数。
struct DeviceState
1 | /** |
其中比较重要的是parent_bus,即设备挂载的总线信息。
初始化
根据前端可知,Qemu使用device_class_base_init()和device_class_init()初始化类,用device_initfn()和device_post_init()初始化对象。
类初始化
TypeInfo的class_base_init字段,即device_class_base_init()函数是初始化父类之后但class_init之前调用,而class_init字段,即device_class_init()就是普通的初始化函数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
55static void device_class_base_init(ObjectClass *class, void *data)
{
DeviceClass *klass = DEVICE_CLASS(class);
/* We explicitly look up properties in the superclasses,
* so do not propagate them to the subclasses.
*/
klass->props_ = NULL;
}
static void device_class_init(ObjectClass *class, void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
VMStateIfClass *vc = VMSTATE_IF_CLASS(class);
ResettableClass *rc = RESETTABLE_CLASS(class);
class->unparent = device_unparent;
/* by default all devices were considered as hotpluggable,
* so with intent to check it in generic qdev_unplug() /
* device_set_realized() functions make every device
* hotpluggable. Devices that shouldn't be hotpluggable,
* should override it in their class_init()
*/
dc->hotpluggable = true;
dc->user_creatable = true;
vc->get_id = device_vmstate_if_get_id;
rc->get_state = device_get_reset_state;
rc->child_foreach = device_reset_child_foreach;
/*
* @device_phases_reset is put as the default reset method below, allowing
* to do the multi-phase transition from base classes to leaf classes. It
* allows a legacy-reset Device class to extend a multi-phases-reset
* Device class for the following reason:
* + If a base class B has been moved to multi-phase, then it does not
* override this default reset method and may have defined phase methods.
* + A child class C (extending class B) which uses
* device_class_set_parent_reset() (or similar means) to override the
* reset method will still work as expected. @device_phases_reset function
* will be registered as the parent reset method and effectively call
* parent reset phases.
*/
dc->reset = device_phases_reset;
rc->get_transitional_function = device_get_transitional_reset;
object_class_property_add_bool(class, "realized",
device_get_realized, device_set_realized);
object_class_property_add_bool(class, "hotpluggable",
device_get_hotpluggable, NULL);
object_class_property_add_bool(class, "hotplugged",
device_get_hotplugged, NULL);
object_class_property_add_link(class, "parent_bus", TYPE_BUS,
offsetof(DeviceState, parent_bus), NULL, 0);
}
可以看到,其主要就是初始化相关的字段和函数指针
对象初始化
而TypeInfo的instance_init字段,即device_initfn()是普通的对象初始化函数,而instance_post_init字段,即device_post_init()函数,是在初始化完父类对象之后再调用。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
26static void device_initfn(Object *obj)
{
DeviceState *dev = DEVICE(obj);
if (phase_check(PHASE_MACHINE_READY)) {
dev->hotplugged = 1;
qdev_hot_added = true;
}
dev->instance_id_alias = -1;
dev->realized = false;
dev->allow_unplug_during_migration = false;
QLIST_INIT(&dev->gpios);
QLIST_INIT(&dev->clocks);
}
static void device_post_init(Object *obj)
{
/*
* Note: ordered so that the user's global properties take
* precedence.
*/
object_apply_compat_props(obj);
qdev_prop_set_globals(DEVICE(obj));
}
可以看到,其主要就是初始化一些相关字段。
实例化
类似于总线,根据类初始化的内容,Qemu使用device_set_realized()来进行实例化
1 | //#0 device_set_realized (obj=0x55555782e5e0, value=true, errp=0x7fffffffd330) at ../../qemu/hw/core/qdev.c:477 |
可以看到,其重点逻辑就是调用类的realize/unrealize函数指针。
后端
在前端小节中介绍过,设备后端指的是Qemu如何处理来自设备前端的数据。
考虑到不同的设备数据处理有不同的特点,因此后端种类十分繁多,这里仅简单罗列一下。
设备类型 | 查找命令 | 设备后端 |
---|---|---|
网络 | qemu-system-x86_64 -netdev help | socket、hubport、tap、user、l2tpv3、bridge、vhost-user、vhost-vdpa |
存储 | blockdev | |
字符设备 | qemu-system-x86_64 -chardev help | ringbuf、mux、pipe、qemu-vdagent、null、msmouse、socket、vc、parallel、memory、udp、file、serial、pty、wctablet、stdio、testdev |
… | … | … |