QEMU中基于QOM的VFIO类的定义

VFIO QOM对象的构造

Everything in QOM is a device

QOM(Qemu Object Model)是QEMU最新的设备模型。QEMU一开始采用ad hoc,每种设备都有不同的表示方式,非常混乱。于是开发了qdev,将所有的模拟设备进行了整合,变成了一种单根结点(系统总线)的树状形式,并且增加了hotplug的功能。后来可能由于Device和Bus之间的复杂关系,又开发了QOM。

QOM是QEMU在C的基础上自己实现的一套面向对象机制,负责将device、bus等设备都抽象成为对象。对象的初始化分为四步:

  1. 将 TypeInfo 注册为 TypeImpl
  2. 实例化 ObjectClass
  3. 实例化 Object
  4. 添加 Property

QEMU将VFIO当做一种设备,因为“Everything in QOM is a device”.

VFIO对象的初始化也符合上面提到的4步流程。

VFIO TypeInfo+TypeImpl

TypeInfo定义

static const TypeInfo vfio_pci_dev_info = { .name = TYPE_VFIO_PCI, //type名 .parent = TYPE_PCI_DEVICE, // 父类名 .instance_size = sizeof(VFIOPCIDevice),// 实例大小 .class_init = vfio_pci_dev_class_init, // 类初始化函数 .instance_init = vfio_instance_init, // 实例初始化函数 .instance_finalize = vfio_instance_finalize, // 实例的动态释放函数 .interfaces = (InterfaceInfo[]) { { INTERFACE_PCIE_DEVICE }, // /* Implemented by devices that can be plugged on PCI Express buses */ { INTERFACE_CONVENTIONAL_PCI_DEVICE }, // /* Implemented by devices that can be plugged on Conventional PCI buses */ { } }, }; static const TypeInfo vfio_pci_nohotplug_dev_info = { .name = TYPE_VFIO_PCI_NOHOTPLUG, .parent = TYPE_VFIO_PCI, .instance_size = sizeof(VFIOPCIDevice), .class_init = vfio_pci_nohotplug_dev_class_init, };

qemu中有2种VFIO定义,一种是vfio_pci_dev_info,另一种是vfio_pci_nohotplug_dev_info。前者包含了类名,父类名,类实例大小,类初始化函数,类实例初始化函数,类实例动态释放函数,还有该类的接口。接口的实现是为热插拔特性做准备。(这个论断不确定,在之后的学习中继续确认)。后者包含了类名,父类名,类实例大小,类初始化函数。

【猜测】这2种VFIO类的区别是,前者可以用于热插拔,因而定义了interfaces,而后者无法热插拔,因此只定义了一个类初始化函数。

创建VFIO ModuleEntry

将用于初始化VFIO定义的entry加入到ModuleTypeList中。

type_init(register_vfio_pci_dev_type) => module_init(register_vfio_pci_dev_type, MODULE_INIT_QOM) => register_module_init(register_vfio_pci_dev_type, MODULE_INIT_QOM) void register_module_init(void (*fn)(void), module_init_type type) { ModuleEntry *e; ModuleTypeList *l; e = g_malloc0(sizeof(*e)); e->init = fn; e->type = type; l = find_type(type); QTAILQ_INSERT_TAIL(l, e, node); }

分配一个新的ModuleEntry,将该entry的init函数设置为register_vfio_pci_dev_type,类型设置为MODULE_INIT_QOM,并将该entry插入到类型为MODULE_INIT_QOM 的 ModuleTypeList 中。在QEMU在main.c(vl.c) 的一开始执行了 module_call_init(MODULE_INIT_QOM) ,它从 init_type_list 中取出对应的 ModuleTypeList ,然后对里面的 ModuleEntry 成员都调用 init 函数,register_vfio_dev_type也会在此时被调用。

TypeImpl注册

register_vfio_pci_dev_type => type_register_static(&vfio_pci_dev_info) type_register_static(&vfio_pci_nohotplug_dev_info) => type_register => type_register_internal struct TypeImpl { const char *name; size_t class_size; size_t instance_size; void (*class_init)(ObjectClass *klass, void *data); void (*class_base_init)(ObjectClass *klass, void *data); void *class_data; void (*instance_init)(Object *obj); void (*instance_post_init)(Object *obj); void (*instance_finalize)(Object *obj); bool abstract; const char *parent; TypeImpl *parent_type; ObjectClass *class; int num_interfaces; InterfaceImpl interfaces[MAX_INTERFACES]; }; static TypeImpl *type_register_internal(const TypeInfo *info) { TypeImpl *ti; ti = type_new(info); // type_table_add(ti); return ti; } static void type_table_add(TypeImpl *ti) { assert(!enumerating_types); g_hash_table_insert(type_table_get(), (void *)ti->name, ti); }

新的VFIO ModuleEntry的init函数为register_vfio_pci_dev_type,该函数经过一系列的调用,最终将TypeInfo转换而成的TypeImpl注册到类型为GHashTable的哈希表中,存储的key值为TypeImpl类型的TypeInfo的name,name在这里指“TYPE_VFIO_PCI”或”TYPE_VFIO_PCI_NOHOTPLUG”。

GHashTable是glib提供的一种hash表,利用key-value机制存储,可以通过key迅速找到其对应的value.

TypeImpl注册完成之后,可以利用g_hash_table_lookup(table,key)的方法在设备类型哈希表中找到VFIO的TypeImpl.

ObjectClass

ObjectClass是QOM的一个重要数据结构,是QOM中所有类的基础,其中的type成员为TypeImpl指针类型。

struct ObjectClass { /*< private >*/ Type type; // 用typedef定义的TypeImpl指针 GSList *interfaces; // Interface的链表(Glib提供的链表类型) const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE]; ObjectUnparent *unparent; GHashTable *properties; // 哈希表,存放该类的属性 };

ObjectClass中的所有成员都是private的,也就是说只有类的内部函数可以访问和修改。这实现了封装特性。

实例化TypeImpl---type_initialize

既然拥有了完整的TypeImpl,下一步就是将该【类型实现结构】实例化,怎样实例化呢?QOM的方式是为TypeImpl中的class成员分配空间,并将TypeImpl(类型实现结构)的相关信息放到class中去。

type_initialize(TypeImpl *ti) => ti->class = g_malloc0(ti->class_size) // 根据类尺寸为ObjectClass分配空间 => parent = type_get_parent(ti) /* 获取父类的TypeImpl,如果父类存在,就: * 1. 完成父类的TypeImpl实例化 * 2. 将父类的obejctclass拷贝到自己的objectclass的最前面 * 3. 创建自己的属性哈希表 * 4. 初始化父类和自己的接口(链表) * 如果父类不存在,只创建自己的属性哈希表即可 */ if(parent) { type_initialize(parent); memcpy(ti->class, parent->class, parent->class_size); ti->class->properties = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, object_property_free); type_initialize_interface(); } else{ ti->class->properties = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, object_property_free); } => ti->class->type = ti; // 为objectClass的类型赋值 => parent->class_base_init; // 如果parent(的TypeImpl)定义了 class_base_init ,调用之 => ti->class_init(ti->class, ti->class_data) // 调用自己的class_init

经历了以上过程,TypeImpl->class就具有了完整的形态,即该TypeImpl拥有了一个完整的实例,那么这个type_initialize到底在哪里调用的呢?

主动调用是指为了调用某函数而发起的调用过程。被动调用是指为了实现某个目的而不得不调用特定函数的调用过程。

// 主动调用 object_class_get_list(const char *implements_type, bool include_abstract) => object_class_foreach => g_hash_table_foreach(object_class_foreach_tramp) => object_class_foreach_tramp => type_initialize

type_initialize的主动调用发生在主动创建type为implements_type的objectClass时发生的。

// 被动调用 object_class_by_name object_class_get_parent object_new_with_type object_initialize_with_type

type_initialize的被动调用发生在获取 class、class的parent、创建type的object、初始化TypeImpl的object时发生。

继承(reference from this site)

从实例化TypeImpl一节可以看出,在创建类对象时,会调用 type_initialize ,其会递归地对 TypeImpl 中的 parent 成员(TypeImpl)递归调用 type_initialize ,然后将创建出来的相应 ObjectClass 拷贝到自己class的最前面。

类对象的第一个成员是 parent_class ,由于父类对象会拷到子类对象的最前面,因此可以认为其指向父类的对象,如此构成链状的继承链,最终指向基类对象 ObjectClass

比如 kvm_accel_type 对应的类对象,该类对象作为叶子类型并没有定义,但其父类 AccelClass 在代码中有定义,其的第一个成员为 ObjectClass ,表示其继承自 ObjectClass 。为了能表示该叶子类型继承 AccelClass ,它修改了 AccelClass的一些对象成员,这样在某种程度上表示了继承关系。比如修改了函数指针成员的指向,相当于实现了虚函数。

又如: register_info 对应的类对象 => PCIDeviceClass => DeviceClass => ObjectClass 构成继承链,最前端的叶子类型通过修改 PCIDeviceClass 成员进行定义。

强制类型转换(reference from this site)

将一个父类的指针转换为子类的指针是不安全的,为了实现这种转换,各类需要提供强制类型转换的宏,如:

#define ACCEL_CLASS(klass) \ OBJECT_CLASS_CHECK(AccelClass, (klass), TYPE_ACCEL) #define OBJECT_CLASS_CHECK(class_type, class, name) \ ((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \ __FILE__, __LINE__, __func__))

如果类对象指针的name和目标子类的name一致,或类对象指针是目标子类的祖先,则执行转换,否则 abort

反过来,从子类指针转换为父类指针是安全的,因为类的第一项就指向父类,访问时不会存在越界等问题。

Object(reference from this site)

Object 属于类实例对象,它是所有类实例对象的基类。

struct Object { /*< private >*/ ObjectClass *class; // 指向类对象 ObjectFree *free; GHashTable *properties; // 维护属性的哈希表 uint32_t ref; // 引用计数 Object *parent; // 指向父类实例对象,实现继承 };

可以看到其第一个成员指向类对象,同时维护有区别于类属性的类实例属性。

创建流程

在QEMU在main.c(vl.c) 的一开始执行了 module_call_init(MODULE_INIT_QOM) ,它从 init_type_list 中取出对应的 ModuleTypeList ,然后对里面的 ModuleEntry 成员都调用 init 函数,init函数将TypeInfo转化为TypeImpl结构并存储在GHashTable中。此后主要根据 TypeImpl 创建 ObjectClassObject

// softmmu/main.c => int main(int argc, char **argv, char **envp) => qemu_init(argc, argv, envp);
// softmmu/vl.c qemu_init => qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, &error_fatal); => device_init_func => qdev_device_add

qemu_opts_foreach

qemu_opts_foreach的原型为:

int qemu_opts_foreach(QemuOptsList *list, qemu_opts_loopfunc func, void *opaque, Error **errp)

作用为针对list中的每一个成员,调用func变量指向的函数,调用形式为func(@opaque, member, @errp).

qemu_find_opts
qemu_find_opts("device") => find_list(vm_config_groups, &"device", &local_err);

qemu_find_opts会从vm_config_groups中找到属于-device(启动qemu时)选项的operations, vm_config_groups是一个二维指针,第一维为QemuOptsList,第二维为QemuOpts,每个元素都为是传入qemu的参数的operation链表。

因此,qemu_find_opts找到属于-device的QemuOptsList之后,device_init_func会对所有的-device命令传入的设备进行初始化。例如,使用以下命令行启动虚拟机:

qemu-system-x86_64 -device vfio-pci,sysfsdev=/sys/bus/pci/devices/0000:00:02.0/

那么qemu_find_opts(“device”)找到的QemuOptsList含有1个QemuOpts,该QemuOpts下面的QemuOpts链表中有2个元素,分别为“vfio-pci”和“sysfsdev=/sys/bus/pci/devices/0000:00:02.0/”。

qdev_device_add
qdev_device_add(QemuOpts *opts, Error **errp) ----------------- find driver -------------------------------------- => driver = qemu_opt_get(opts, "driver") // opts中是否存在“driver”,不存在driver则直接报parameter error错误 这里driver是个字符串变量,代表opt->str => qdev_get_device_class(&driver, errp) // 获取driver对应的DeviceClass => oc = object_class_by_name(*driver) // 获得driver对应的ObjectClass => object_class_dynamic_cast(oc, TYPE_DEVICE) // 检查ObjectClass是否可以进行正确的类型转换(是否符合继承特性) => object_class_is_abstract(oc) // 检查ObjectClass是否为抽象类型,任何ObjectClass必须为抽象类型 => dc = DEVICE_CLASS(oc) // 将ObjectClass转换为DeviceClass => return dc // 返回VFIO的DeviceClass [TypeInfo->TypeImpl->ObjectClass->DeviceClass] ------------------------ find bus ---------------------------------- => path = qemu_opt_get(opts, "bus"); // opts中是否存在"bus",并返回bus的路径 => bus = qbus_find(path, errp) // 查找bus // bus部分不深追,再找机会系统了解 ------------------------- ensure whether to hide device ------------ hide = should_hide_device(opts); if (hide) { return NULL; } ------------------------- create Device ---------------------------- /* 经过DEVICE(Obejct_new(driver))之后,会获得由object转化来的DeviceState实例 */ dev = DEVICE(object_new(driver)) // 将以driver(vfio)为模板生成的obejct转化为设备状态类 => object_new(driver) => ti = type_get_by_name(typename) => object_new_with_type(ti) => type_initialize => object_initialize_with_type => type_initialize => object_class_property_init_all => g_hash_table_new_full // 创建TypeImpl->class的属性哈希表 => object_init_with_type => ti->instance_init(obj) // 调用TypeImpl的实例初始化函数,对于VFIO来说就是vfio_instance_init => object_post_init_with_type(obj, type) => // 如果obj有自己的instance_post_init函数,就调用自己的,如果没有,就调用父类的该函数,如此递归调用。instance_post_init是在所有instance_init函数调用完毕之后,对object实例化的完成函数,其内容为object设置一些全局属性,类似于是否支持热插拔等。 -------------------------------------------------------------------- qdev_set_parent_bus(dev, bus) // 设置driver对应的总线 qdev_set_id(dev, qemu_opts_id(opts)) // 设置deviceState的ID --------------------------- set properties ------------------------- qemu_opt_foreach(opts, set_property, dev, &err) // 对opts中除了driver和bus的部分,通过set_properties为该object设置属性 => set_property => prop->set // object的property的set方法目前无法得知,需要debug看 object_property_set_bool(OBJECT(dev), true, "realized", &err) // 将deviceState转换为Object,将其的"realized"属性设为true.注意,这里已经完成了vfio_realize的调用 -------------------------------------------------------------------- return dev; // 返回deviceState
object_class_by_name

值得注意的是,在获取driver对应的DeviceClass的oc = object_class_by_name(*driver)步骤中:

object_class_get_name => type_get_by_name => type_table_lookup => g_hash_table_lookup => type_initialize

首先根据driver(其实是TypeImpl的name)从哈希表中找到了对应的TypeImpl,然后利用type_initialize对该TypeImpl->class进行了初始化,对于VFIO的TypeImpl,会调用vfio_pci_dev_class_init, 即vfio_pci_dev_class_init(ti->class, ti->class_data)

static void vfio_pci_dev_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); // 将ObjectClass转化为DeviceClass(抽象类到Device子类) PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass); // 将ObjectClass转化为PCIDeviceClass(抽象类到PCIDevice子类) dc->reset = vfio_pci_reset; device_class_set_props(dc, vfio_pci_dev_properties); // 设置DeviceClass的属性,即set设置DeviceClass结构的一些bit dc->desc = "VFIO-based PCI device assignment"; set_bit(DEVICE_CATEGORY_MISC, dc->categories);// 将该DeviceClass的类型标记为杂项设备(MISC) /* 设置PCIDeviceClass的realize,exit,PCI读写对应的方法 */ pdc->realize = vfio_realize; pdc->exit = vfio_exitfn; pdc->config_read = vfio_pci_read_config; pdc->config_write = vfio_pci_write_config; }

所以经过object_class_get_name后,获得的ObjectClass具有了完整的结构,其作为Device类时,拥有了被reset的途径,获得了设备属性分类,设备描述;其作为PCI设备类时,拥有了realize,exit,读写PCI配置空间的方式,其中,realize是QEMU感知到设备的感知函数,只有经过realize,一个设备才能真正在guest中被检测到。

find bus

bus部分不深追,再找机会系统了解,大致意思就是根据用户传入的-device后跟的总线信息,找到系统总线并赋值给bus相关信息。

ensure whether to hide device

qemu中的设备,只有在非hide的情况下,才能被Guest检测到。

create Device

这部分是创建设备的核心,通过一系列函数调用,将最开始的TypeImpl彻底实例化,并强制转换出一个对应的DeviceState类来,供qemu使用。

set properties

对qemu传入的opts中除了driver和bus的其它属性进行设置,设置对象为device对应的Object.

经过qdev_device_add,创建了device对应的对象实例,并返回了device对应的设备状态结构。device_init_func最后还调用了finalize对整个设备初始化过程中使用的Object进行释放,因为Object类本身只是个抽象类,完成为对象实例初始化的功能之后就要释放。

但是,只是构造了对象实例,具体的realize还未调用,Guest还是无法使用该设备对象。realize函数的调用因设备而异,VFIO的realize函数的调用在下一节。

VFIO的realize

前面提到了VFIO这个QOM设备的realize函数为vfio_realize,只有在经过vfio_realize()之后,才能建立传入的VFIO设备和Guest之间的联系,那么vfio_realize在qemu什么阶段进行调用呢?

main => qemu_init => device_init_func => qdev_device_add => object_property_set_bool => object_property_set_qobject => object_property_set => property_set_bool => device_set_realized => pci_qdev_realize => vfio_realize

也就是说,在运行创建流程中提到的qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, &error_fatal)就已经完成了vfio_realize,即传入的VFIO设备的最后构建。


__EOF__

本文作者EwanHai
本文链接https://www.cnblogs.com/haiyonghao/p/14440761.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   EwanHai  阅读(906)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示