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等设备都抽象成为对象。对象的初始化分为四步:
- 将 TypeInfo 注册为 TypeImpl
- 实例化 ObjectClass
- 实例化 Object
- 添加 Property
QEMU将VFIO当做一种设备,因为“Everything in QOM is a device”.
VFIO对象的初始化也符合上面提到的4步流程。
VFIO TypeInfo+TypeImpl
TypeInfo定义
qemu中有2种VFIO定义,一种是vfio_pci_dev_info
,另一种是vfio_pci_nohotplug_dev_info
。前者包含了类名,父类名,类实例大小,类初始化函数,类实例初始化函数,类实例动态释放函数,还有该类的接口。接口的实现是为热插拔特性做准备。(这个论断不确定,在之后的学习中继续确认)。后者包含了类名,父类名,类实例大小,类初始化函数。
【猜测】这2种VFIO类的区别是,前者可以用于热插拔,因而定义了interfaces,而后者无法热插拔,因此只定义了一个类初始化函数。
创建VFIO ModuleEntry
将用于初始化VFIO定义的entry加入到ModuleTypeList中。
分配一个新的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注册
新的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指针类型。
ObjectClass中的所有成员都是private的,也就是说只有类的内部函数可以访问和修改。这实现了封装特性。
实例化TypeImpl---type_initialize
既然拥有了完整的TypeImpl,下一步就是将该【类型实现结构】实例化,怎样实例化呢?QOM的方式是为TypeImpl中的class
成员分配空间,并将TypeImpl(类型实现结构)的相关信息放到class中去。
经历了以上过程,TypeImpl->class就具有了完整的形态,即该TypeImpl拥有了一个完整的实例,那么这个type_initialize到底在哪里调用的呢?
主动调用是指为了调用某函数而发起的调用过程。被动调用是指为了实现某个目的而不得不调用特定函数的调用过程。
type_initialize的主动调用发生在主动创建type为implements_type的objectClass时发生的。
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)
将一个父类的指针转换为子类的指针是不安全的,为了实现这种转换,各类需要提供强制类型转换的宏,如:
如果类对象指针的name和目标子类的name一致,或类对象指针是目标子类的祖先,则执行转换,否则 abort
反过来,从子类指针转换为父类指针是安全的,因为类的第一项就指向父类,访问时不会存在越界等问题。
Object(reference from this site)
Object 属于类实例对象,它是所有类实例对象的基类。
可以看到其第一个成员指向类对象,同时维护有区别于类属性的类实例属性。
创建流程
在QEMU在main.c(vl.c) 的一开始执行了 module_call_init(MODULE_INIT_QOM) ,它从 init_type_list 中取出对应的 ModuleTypeList ,然后对里面的 ModuleEntry 成员都调用 init 函数,init函数将TypeInfo转化为TypeImpl结构并存储在GHashTable中。此后主要根据 TypeImpl 创建 ObjectClass 和 Object
qemu_opts_foreach
qemu_opts_foreach的原型为:
作用为针对list
中的每一个成员,调用func
变量指向的函数,调用形式为func(@opaque, member, @errp)
.
qemu_find_opts
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_find_opts(“device”)找到的QemuOptsList含有1个QemuOpts,该QemuOpts下面的QemuOpts链表中有2个元素,分别为“vfio-pci”和“sysfsdev=/sys/bus/pci/devices/0000:00:02.0/”。
qdev_device_add
object_class_by_name
值得注意的是,在获取driver对应的DeviceClass的oc = object_class_by_name(*driver)
步骤中:
首先根据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)
所以经过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什么阶段进行调用呢?
也就是说,在运行创建流程
中提到的qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, &error_fatal)
就已经完成了vfio_realize,即传入的VFIO设备的最后构建。
__EOF__

本文链接:https://www.cnblogs.com/haiyonghao/p/14440761.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了