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设备的最后构建。

posted @ 2021-02-24 12:57  EwanHai  阅读(808)  评论(0编辑  收藏  举报