zl程序教程

您现在的位置是:首页 >  其他

当前栏目

QOM(QEMU)设备管理机制概述

2023-04-18 16:57:29 时间

QEMU的对象管理是很重要的一个部分,linux中一切皆文件,而qemu中虚拟的一切实体皆Object,不管是CPU、设备还是KVM的使用都离不开QOM框架(Qemu Object Module)。

本文对QOM设备管理机制做简单清晰的介绍,会以VIRTIO设备举例说明,方便更好的理解。

一、ObjectClass和Object数据结构

QOM采用了C++里面向对象的命令方式,每一类对象会实例化一个ObjectClass,类的成员是这类对象通用的内容,比如一些统一的操作接口(方法)、VIRTIO设备的PCI VENDOR/DEVICE ID等。

QOM的每一个设备会实例化一个Object,对象的成员是每个设备独有的内容,比如VIRTIO设备的FEATURE、队列等。

另外,面向对象里继承和派生的概念也在QOM中得到了很好的运用,所有的设备都有一个基类,然后根据设备的分层,逐渐向上派生。

单纯讲QEMU中设备的分层,可以参考内核驱动的分层,内核的设备驱动也是面向对象的C语言实现形式,所有的设备和驱动基类是struct device和struct driver结构。如果是一个virtio网卡设备,则首先会封装传输层的pci设备+驱动(pci_dev和pci_driver);然后再上层会是应用层virtio-net设备+驱动(virtio_device和virtio_driver),当然在内核里virtio_deivce这一层也是从device派生的,不是从pci_device派生的。内核里除了设备和驱动,还有总线的概念,这些与qemu是不一样的。

有看到一种表述形式是将QEMU里的Class形容为驱动,Object形容为设备;确实Class里主要的内容是这类设备的通用操作接口,而且ObjectClass也会实例化一个实体,这个实体是一类设备共用的,而这类设备的每一个实际的设备都对应一个Object;Object会有一个指针指向ObjectClass。所以从这个角度来说Object和ObjectClass对应设备和驱动是适合的。

QOM的基类是ObjectClass,对应的实体是Object。根据Class和Object逐层向上派生新的Class和Object。下面以virtio-net设备举例:

1、QOM最下层的基类是ObjectClass,对应的实体是Object。

2、设备的类DeviceClass,对应的实体是DeviceState;

3、PCI设备又在设备的基础上派生出了PCIDeviceClass,对应的Object是PCIDevice;

4、virtio-pci设备又会在PCI设备的基础上派生出VirtioPCIClass,对应的Object是VirtIOPCIProxy;

5、virtio-net设备又在VIRTIO-PCI设备的基础上派生出新的类(实际上因为virtio-pci的net设备对比virtio-pci设备,并不需要增加新的内容,所以这一层派生的是同样的结构VirtioPCIClass,或者理解为直接使用上层的VirtioPCIClass),对应的Object却是需要增加net相关的新内容,所以派生出了新的结构体VirtIONetPCI。

本质上C语言里派生的实现形式就是结构体的包含嵌套,派生类层层包含新的类,对应结构体的框图如下。

另外,为了更好的在派生结构体之间互相引用,通常把被引用的结构体放在自己的第一个字段,这也是种C语言实现的潜规则。这样对于一个VirtIONetPCI实体的指针来说,其上层的VirtioPCIProxy、PCIDevice、DeviceState、Object数据指针就是它自己。

struct DeviceClass {
    /*< private >*/
    ObjectClass parent_class;
    /*< public >*/

    DECLARE_BITMAP(categories, DEVICE_CATEGORY_MAX);
    const char *fw_name;
    const char *desc;

    Property *props_;

    bool user_creatable;
    bool hotpluggable;

    /* callbacks */
    /*
     * Reset method here is deprecated and replaced by methods in the
     * resettable class interface to implement a multi-phase reset.
     * TODO: remove once every reset callback is unused
     */
    DeviceReset reset;
    DeviceRealize realize;
    DeviceUnrealize unrealize;

    /* device state */
    const VMStateDescription *vmsd;

    /* Private to qdev / bus.  */
    const char *bus_type;
};

截取一个DeviceClass的结构体定义,对Class有大概的了解和认识。看DeviceClass的定义,首先在第一个字段定义了他的父类ObjectClass,后面是派生的DeviceClass自己的数据结构。对于设备来说,会有通用的设备  reset、realize(类似于初始化)、unrealize(设备弹出?)接口,具体的调用时机待添加。VMStateDescription *vmsd结构体是迁移的时候需要的很重要的结构,每个设备都要实现相应的接口和数据。

二、TypeInfo数据结构

第一节描述了关键的ObjectClass和Object结构,以及QOM基于这两个基础结构体衍生的对象管理模型。那么从代码实现的角度来看,对于框架性的QOM来说,旨在提供一种方便易实现的方式给各个模块使用。

那么,QOM对外提供了第三个结构TypeInfo。这个TypeInfo是跟ObjectClass和Object并列的结构,每一类设备不仅对应一种ObjectClass和Object结构,而且对应一个TypeInfo结构。

而且TypeInfo结构是统一的,并非ObjectClass和Object那样自下而上派生出不同的结构。TypeInfo结构的目的是为了实例化出相应的ObjectClass和Object,在每个对象的代码实现中,会注册一个TypeInfo结构到QOM框架中。QOM内部会维护一个全局的TypeTmpl(在QOM内部会把TypeInfo转化成相近的结构体TypeTmpl)的hash表,当QMP命令生产一个对象(比如VIRTIO-NET-PCI设备)的时候,会从表中找到相应的TypeImpl,然后根据TypeImpl的内容去实例化相应的ObjectClass(如果已经注册过相同类型的设备,则已有ObjectClass,无需实例化)和Object。 

struct TypeInfo
{
    const char *name;
    const char *parent;

    size_t instance_size;
    size_t instance_align;
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);

    bool abstract;
    size_t class_size;

    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
    void *class_data;

    InterfaceInfo *interfaces;
};

这就是TypeInfo结构,可以看到他有对class(ObjectClass)和instance(object)的一组数据,分别用于申请一个class结构、instance结构(class_size、instance_size)。申请之后,还会调用相应的初始化接口进行初始化(class_init、instance_init)。

结合第一节中virtio-net-pci设备的QOM结构图进行理解,该类设备的实现中instance_size就是sizeof(VirtIONetPCI),class_size=0的意思是沿用其父类设备的class_size,我们有单独介绍virtio-net-pci这一层因为没有相对于其parent class(virtio-pci)的特有数据,所以是没有单独的ObjectClass结构的,他实例化的就是parent结构VirtioPciClass。

下图中是以virtio-net-pci类型设备为例描述了:

1、三个数据结构的关系

2、QOM流程在virtio-net-pci设备的体现。

TypeImpl(约等于TypeInfo)、VirtIOPciClass和VirtIOPciNet的数据关系见下图。

  另外,红色的字体描述了QOM框架实现一个设备的流程,还算是比较清晰。第一步中”virtio-net-pci“的字符串来源又是哪里呢,贴一个QEMU使用virtio-net-pci设备的命令行,就是最后一行的“-device virtio-net-pci"指定了这个设备类型,"-device"后面指定了”virtio-net-pci"。

./x86_64-softmmu/qemu-system-x86_64 
    -machine accel=kvm -cpu host -smp sockets=2,cores=2,threads=1 -m 3072M 
    -object memory-backend-file,id=mem,size=3072M,mem-path=/dev/hugepages,share=on 
    -hda /home/kvm/disk/vm0.img -mem-prealloc -numa node,memdev=mem 
    -vnc 0.0.0.0:00 -monitor stdio --enable-kvm 
    -netdev type=tap,id=eth0,ifname=tap30,script=no,downscript=no 
    -device e1000,netdev=eth0,mac=12:03:04:05:06:08 
    -chardev socket,id=char1,path=/tmp/vhostsock0,server 
    -netdev type=vhost-user,id=mynet3,chardev=char1,vhostforce,queues=$QNUM 
    -device virtio-net-pci,netdev=mynet3,id=net1,mac=00:00:00:00:00:03,disable-legacy=on

总结

文章从最上层的结构体描述了QOM的框架,细节的东西都没有涉及,后续需要添加一些内容。考虑添加virtio-pci-net设备具体的初始化流程,包括DeviceClass的realize接口,另文章更全面易懂。