qemu-kvm qbus和qdev模型

本文以qemu-kvm-1.5.3代码分析qemu-kvm qbus和qdev实现机制。qemu-kvm中CPU,网卡,虚拟磁盘等一切皆设备,这些设备的实现都是基于DeviceState, BusState等基本数据结构实现。

1. DeviceState等基本数据结构间关系

DeviceState,BusState,Object, ObjectClass等数据结构是qemu-kvm设备模型的基本数据结构。其相互间关系如下图所示。

62cb8236e002d189eaa26ccda8e3d4fe_2020-05-13-18-16-20.png

1.1 DeviceState数据结构

DeviceState即设备状态,其定义如下:

struct DeviceState {
    /*< private >*/
    Object parent_obj;
    /*< public >*/

    const char *id;
    bool realized;
    QemuOpts *opts;
    int hotplugged;
    BusState *parent_bus;
    int num_gpio_out;
    qemu_irq *gpio_out;
    int num_gpio_in;
    qemu_irq *gpio_in;
    QLIST_HEAD(, BusState) child_bus;
    int num_child_bus;
    int instance_id_alias;
    int alias_required_for_version;
};
  1. parent_obj,当前代码中并没有使用
  2. id 设备id,当添加设备时会将opts中id赋值给设备
  3. realized,表示设备是否已经实现,以cpu为例,当创建cpu完成后,会调用object_property_set_bool(OBJECT(cpu), true, "realized", &local_err)设置realized为true,调用栈如下:
#0  device_set_realized (obj=0x555557568000, value=true, err=0x7fffffffdcf0) at hw/core/qdev.c:676
#1  0x00005555556fde7e in property_set_bool (obj=0x555557568000, v=<optimized out>, opaque=0x555556cdf960, name=<optimized out>, errp=0x7fffffffdcf0) at qom/object.c:1302
#2  0x00005555556fff57 in object_property_set_qobject (obj=0x555557568000, value=<optimized out>, name=0x55555585a16a "realized", errp=0x7fffffffdcf0) at qom/qom-qobject.c:24
#3  0x00005555556ff140 in object_property_set_bool (obj=obj@entry=0x555557568000, value=value@entry=true, name=name@entry=0x55555585a16a "realized", errp=errp@entry=0x7fffffffdcf0)
    at qom/object.c:853
#4  0x00005555557619ae in pc_new_cpu (cpu_model=cpu_model@entry=0x7fffffffe54a "host", apic_id=0, icc_bridge=icc_bridge@entry=0x555557560000, errp=errp@entry=0x7fffffffdd30)
    at /home/lrs/project/new_qemu_libvirt_repo/qemu-kvm-1_5_3_160_el7_pa/hw/i386/pc.c:916
#5  0x00005555557629ff in pc_cpus_init (cpu_model=0x7fffffffe54a "host", icc_bridge=icc_bridge@entry=0x555557560000)
    at /home/lrs/project/new_qemu_libvirt_repo/qemu-kvm-1_5_3_160_el7_pa/hw/i386/pc.c:972
#6  0x0000555555763e8e in pc_init1 (pci_enabled=1, kvmclock_enabled=1, system_io=0x555556d38210, system_memory=0x555556d38160, args=0x7fffffffdf8
  1. opts, 指向创建这一设备所用的选项,qemu-kvm在解析启动命令时会根据参数生成opts,再根据opts创建设备对象
  2. parent_bus, 指向父bus,任何设备创建都会调用qdev_try_create(bus, type),该函数的bus参数即设备父bus,函数中调用qdev_set_parent_bus()为设备设置父bus
  3. gpio_out, gpio_in, 当信号发送到设备的某个PIN时要调用设备相应的gpio_in的handler,表示其状态发生变化。如果需要输出,则调用gpio_out相应的handler,模拟信号从一个设备发往另外某个设备。从目前qemu-kvm用户态的实现来看,这些handler并没有直接基于设备的调用点
  4. child_bus,设备的子总线,qbus_create()调用qbus_realize()实现子bus时会将子bus插入到父设备的子bus链表。

1.2 BusState数据结构

BusState数据结构如下所示:

struct BusState {
    Object obj;
    DeviceState *parent;
    const char *name;
    int allow_hotplug;
    int max_index;
    QTAILQ_HEAD(ChildrenHead, BusChild) children;
    QLIST_ENTRY(BusState) sibling;
};
  1. obj,BusState的对象,对于BusState的实现,需要通过object找到object_class,基于对象类相应的函数对BusState进行实例化
  2. parrent, BusState的父设备
  3. name,bus名称
  4. allow_hotplug,是否允许热插拔
  5. children, 该BusState的子bus
  6. sibling,该BusState的兄弟BusState

1.3 Object和ObjectClass

Object数据结构如下:

struct Object
{
    /*< private >*/
    ObjectClass *class;
    ObjectFree *free;
    QTAILQ_HEAD(, ObjectProperty) properties;
    uint32_t ref;
    Object *parent;
};
  1. class,指向Object具体的类
  2. free(),释放Object使用的callback
  3. properties, Object属性
  4. ref, refcount
  5. parrent,指向父对象

ObjectClass如下所示:


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_finalize)(ObjectClass *klass, void *data);

    void *class_data;

    void (*instance_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];
};

struct ObjectClass
{
    /*< private >*/
    Type type;
    GSList *interfaces;

    const char *cast_cache[OBJECT_CLASS_CAST_CACHE];

    ObjectUnparent *unparent;
};

其中class_init()函数用于初始化类,class_base_init(),所有ObjectClass实现基于TypeInfo。

2.i8259设备实现

以i8259为例,分析其实现机制

2.1 i8259 class注册

TypeInfo i8259_info定义如下:

static const TypeInfo i8259_info = {
    .name       = "isa-i8259",
    .instance_size = sizeof(PICCommonState),
    .parent     = TYPE_PIC_COMMON,
    .class_init = i8259_class_init,
};

type_init(pic_register_type)是一个__attribute__((constructor))属性的函数,这种属性的函数在main()函数开始之前会被调用。即main()函数开始之前会调用pic_register_types()-->type_register_static(&i8259_info)注册i8259 class。

对于type_register_internal(),其实现主要就是将TypeInfo中的name, parent,class_size,各种callback如class_init(),class_base_init(),class_finalize()等函数, interface赋值给TypeImpl。

对于i8259,其TypeImpl最终如下图所示:

59dd0e45a8386479b9db51bed72a0e1d_2020-05-13-11-00-58.png

2.2 i8259初始化

i8259设备初始化函数为i8259_init(),QEMUMachine数据结构中会有一callback QEMUMachineInitFunc *init,当qemu-kvm的main()函数在解析完用户参数时,会调用machine->init(&args)初始化虚拟机,当前,qemu-kvm定义了多种machine, 以x86平台为例,有:pc-i440fx-rhel7.0.0, rhel6.6.0, rhel6.4.0, rhel6.3.0, rhel6.2.0, pc-q35-1.5, pc-q35-1.4等等,这些machine的注册都通过machine_init()函数进行注册,具体地也类似type_init(),是一个__attribute__((constructor))函数,在main()函数开始之前就已经执行。

Note: qemu-kvm的main()函数中,会基于-M &dollar;machine, 或- machine machine=&dollar;machine,获取machine类型,然后调用machine_parse()函数获取具体的machine。本文以"pc-i440fx-rhel7.0.0"为例,diable kvm_kernel_irqchip(-machine参数append kernel_irq=off)为例,分析i8259设备初始化流程,由pc_init1()函数调用。

在初始化i8259设备之前,首先会通过pc_allocate_cpu_irq()函数分配cpu_irq, 然后将cpu_irq[0]作为i8259设备的父irq, 初始化i8259设备。pc_allocate_cpu_irq()即通过qemu_allocate_irqs(pic_irq_request, NULL, 1)分配一个数组元素个数为1的qemu_irq数组,即handler=pic_irq_requst, opaque = NULL, array_size = 1。cpu_irq如下图所示,pic_irq_request()用于向vcpu注入中断。

53ff92225a29f86b457ddc74bac2032b_2020-05-12-19-57-57.png

分析i8258_init()函数,其主要流程如下图所示,最终创建2个i8259设备,其中一个是master,另一个是slave:

5de87c72d5f6cafd90abac53464dbca6_2020-05-12-20-30-02.png

其关键函数为i8259_init_chip()函数,流程如下:

afc7f0949c4564f0c02d596bb4823ff0_2020-05-13-13-49-57.png

2.3 object_class_by_name()函数

object_class_by_name()函数基于TypeName初始化类,其具体实现如下:

c23a0cecbf37b50386b9638f07308946_2020-05-13-18-11-22.png

以type_initialize("isa-i8259")函数为例,最终初始化生成的isa-i8259 TypeImpl如下,主要实现了TypeImpl的class相关的数据结构,主要是初始化类的内部状态和函数实现指针。

b7d5d9017b9214e623f341698d26ba01_2020-05-13-19-26-01.png

2.4 object_new_with_type(i8259_impl)实例化

object_new_with_type()函数用于实例化对象,其流程如下:

3a19a997b20bb6eb514e15f611e69a34_2020-05-13-20-13-15.png

对于isa-i8259,由于先递归,再调用type->instance_init(),其实例化过程执行流实际是先执行祖先的instance_init(),即object_instance_init()--->device_initfn()--->isa_device_init()。

  • object_instance_init()函数主要设置"type" 属性
  • device_initfn()用于设备实例初始化,主要是

    1. 当虚拟机创建完成后的设备init,会设置其为hotplugged状态,但此时realized = false
    2. 添加"realized"属性
    3. 将自身class的props和父类的属性加入到设备属性状态
    4. 将global定义的设备属性加入到设备属性状态
    5. 将&dev->parent_bus作为"parent_bus"属性opaque加入到device属性链表(此时还未设置parent_bus,所有使用其地址加入)
  • isa_device_init()函数主要将isairq[0]和isairq[1]都初始化为-1

设备初始化完成后如下:

4a031fdfbd0fe9089f3b2a6df303d119_2020-05-14-11-30-35.png

2.5 qdev_set_parent_bus()

将设备插入到parent_bus中。

2.6 设备实例化

在isa-i8259设备初始化完成后,最后悔调用qdev_init()函数对设备进行实例化,该函数会调用device_set_realized()对设备进行实例化。

device_set_realized()函数实现逻辑如下:

  1. 通过object_property_add_child()将设备对象以名称"device[]"挂到容器目录root/machine/unattached/目录,如下所示

4cdcdbad7f014d8c0ed0896baaa3c241_2020-05-14-14-10-37.png

  1. 调用dc->realize()即device_realize对设备进行实例化:
device_realize(dev)
    -->dc = DEVICE_GET_CLASS(dev)
    -->dc->init(dev) // isa_device_class_init()中会将该callback赋值为isa_qdev_init
    -->isa_qdev_init(dev);
        -->klass = ISA_DEVICE_GET_CLASS(dev)
        -->klass->init(dev)// pic_common_class_init()将该callback赋值为pic_init_common
        -->pic_init_common(dev)
            -->s = PIC_COMMON(dev)
            -->info = PIC_COMMON_GET_CLASS(s)
            -->info->init(s)
                -->pic_init(s)
                -->isa_register_ioport()
                -->...
  1. 更新isa i8259 device vmsd相关域
  2. dev->realized = true,即完成设备实例化

3. isa-i8259设备中断产生机制

对于目前在x86架构上使用qemu-kvm的虚拟机,实际并没有直接使用isa-i8259设备的中断。或者是通过gsi(global system interrupt)调用,或者通过pit timer调用。


LanceLiu89
0 声望0 粉丝