基本介绍
本文直接从VirtIO开始分析,在前期需要一些基础只是特别是Qemu的QOM对象模型,有很多其他的技术文档都分析的很好。VirtIONETPCI设备同样遵循QOM模型。VirtIONetPCI是一个半虚拟化的网卡设备,在guest os中存在virtio网络驱动对其进行操作,在qemu中的VirtIONetPCI是virtio网络的后端,它使用host os提供的网络资源来模拟虚拟机的网络设备。VirtIONetPCI即是一个PCI设备又是一个VirtIO设备,在guest os中,它的表现跟传统的PCI设备一样,guest os的PCI设备扫描时能够扫描到该设备,并加载驱动进行执行。
VirtIO设备模拟分析需要了解一些VirtIO的资料,后续文章慢慢补充,本文先讲解的是VirtIO的QOM过程,暂时不太需要这些知识。
正文
1. VirtIONetPCI设备的继承关系
VirtIONetPCI设备是一个多重继承的设备,其定义如下所示:
struct VirtIONetPCI {
VirtIOPCIProxy parent_obj;
VirtIONet vdev;
};
VirtIONetPCI包含有两个字段: VirtIOPCIProxy和VirtIONet,它是多重集成,即是一个PCI设备,又是一个VirtIO设备。
VirtIOPCIProxy代表一个PCI设备,它以QOM正常的继承方式继承父类PCIDeviceClass的,初始化也是以正常的方式(使用object_new)来初始化。其继承链是:
Object->Device->PCIDevice->VirtIOPCIProxy->VirtIONetPCI
VirtIONet以组合的方式集成到VirtIONetPCI中,它不能以正常的QOM对象的初始化方式初始化,在VirtIONetPCI初始化的最后一步,会调用type_impl(TYPE_VIRTIO_NET_PCI)->instance_init进行对象初始化,在该函数中调用virtio_instance_init_common->object_initialize函数初始化其内部对象VirtIONet字段,就像C++中的一个对象数据成员的初始化,在构造函数中调用数据成员的构造函数。其继承链是:
Object->Device->VirtIODevice->VirtIONet
VirtIONetPCI的初始化分为类初始化和对象初始化两部分,两条继承链都要初始化,QOM的类和对象的初始化顺序来说,其初始化顺序为:
先初始化继承链上的类型(按从左到右的顺序):
Objectclass->DeviceClass->PCIDeviceClass->VirtioPCIClass->VirtIoNetPCIClass -->VirtIODevice->VirtIONet
再初始化继承链上的对象(按从左到右的顺序):
Object->DeviceState->PCIDevice->VirtIOPCIProxy->VirtIONetPCI–>VirtIODevice->VirtIONet
2. VirtIONetPCI的类型的初始化
VIrtIONetPCI类型的初始化是沿着类继承链来初始化的,在qemu启动时,每次使用object_new初始化一个对象时,它首先会判断该对象的类型有没有初始化,如果没有初始化就调用type_initialize(Type)来初始化该对象的类,该函数会沿着继承链依次初始化每个类型,其中父类型会拷贝到子类型中的父类型字段中。
最先初始化的是ObjectClass,其代码如下:
static TypeInfo object_info = {
.name = TYPE_OBJECT,
.instance_size = sizeof(Object),
.instance_init = object_instance_init,
.abstract = true,
};
可以看到类型TYPE_OBJECT(ObjectClass)并没有class_init函数,也就是说该类型初始化时并不需要做特殊的初始化操作,这是自然的,对象Object和类型ObjectClass并没有模拟一个有意义的设备,只是为了构建面向对象继承关系的工具结构体,这意味着ObjectClass类型初始化时只是分配了一个struct ObjectClass结构体,放在全局类型hash表中。
接下来要初始化的是DeviceClass类型:
static 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),
};
85 typedef struct DeviceClass {
87 ObjectClass parent_class;
90 DECLARE_BITMAP(categories, DEVICE_CATEGORY_MAX);
91 const char *fw_name;
92 const char *desc;
93 Property *props;
106 bool user_creatable;
107 bool hotpluggable;
110 DeviceReset reset;
111 DeviceRealize realize;
112 DeviceUnrealize unrealize;
115 const struct VMStateDescription *vmsd;
118 const char *bus_type;
119 } DeviceClass;
1040 static void device_class_init(ObjectClass *class, void *data)
1041 {
1042 DeviceClass *dc = DEVICE_CLASS(class);
1043
1044 class->unparent = device_unparent;
1052 dc->hotpluggable = true;
1053 dc->user_creatable = true;
1054 }
DeviceClass类型继承自ObjectClass,它是qemu要模拟的所有类型的父类型,定义了设备的通用属性和方法。
- parent_class : 指向父类型的指针。
- fw_name : firware名称
- propertys: 属性,在qemu中,每个设备都有各自的属性,以链表的形式保存在设备的根结构Object的properties字段中。但是每个设备有哪些属性,是在设备的类型中定义的,即这里的properties字段定义设备有哪些属性,设备类型的继承链上每个子类型都有可能定义一些属性,在设备初始化时,会把遍历设备类型继承链,把所有属性都存放到Object的属性链表中。
- user_creatable:设备是否是可以由用户创建的,理论上所有外设都应该是用户可创建的,这个字段不应该存在,但是有些设备会由于某些隐秘的原因会初始化失败,把这些失败信息反馈给用户是比较残酷的,所以设置该字段。
- hotpluggable: 设备是否是可插拔的
- reset: 设备复位回调函数
- realize: 设备实例化回调函数。qemu的设备初始化分为两步,一个是设备类型中定义的构造函数(instance_init),自创建设备(object_new)时调用,另外一个是这里的realize函数,在设备的realied属性被设置为true时调用。
- unrealize: 与realize回调函数对应,设备清理时调用。
- VMStateDescription:该结构体用来保存设备的状态,在虚拟机迁移或冻结时使用。
- bus_type:总线类型,在qdev的设备模型中,每个设备都有其挂接的总线。
上面介绍了通用设备类型的一些属性和函数接口,device_class_init对通用设备类型做初始化,它很简单,默认设置设备为用户创建的和可热插拔的。
DeviceClass类初始化后,接下来初时候PCIDeviceClass。