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设备模型的基本数据结构。其相互间关系如下图所示。
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;
};
- parent_obj,当前代码中并没有使用
- id 设备id,当添加设备时会将opts中id赋值给设备
- 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
- opts, 指向创建这一设备所用的选项,qemu-kvm在解析启动命令时会根据参数生成opts,再根据opts创建设备对象
- parent_bus, 指向父bus,任何设备创建都会调用qdev_try_create(bus, type),该函数的bus参数即设备父bus,函数中调用qdev_set_parent_bus()为设备设置父bus
- gpio_out, gpio_in, 当信号发送到设备的某个PIN时要调用设备相应的gpio_in的handler,表示其状态发生变化。如果需要输出,则调用gpio_out相应的handler,模拟信号从一个设备发往另外某个设备。从目前qemu-kvm用户态的实现来看,这些handler并没有直接基于设备的调用点
- 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;
};
- obj,BusState的对象,对于BusState的实现,需要通过object找到object_class,基于对象类相应的函数对BusState进行实例化
- parrent, BusState的父设备
- name,bus名称
- allow_hotplug,是否允许热插拔
- children, 该BusState的子bus
- sibling,该BusState的兄弟BusState
1.3 Object和ObjectClass
Object数据结构如下:
struct Object
{
/*< private >*/
ObjectClass *class;
ObjectFree *free;
QTAILQ_HEAD(, ObjectProperty) properties;
uint32_t ref;
Object *parent;
};
- class,指向Object具体的类
- free(),释放Object使用的callback
- properties, Object属性
- ref, refcount
- 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最终如下图所示:
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 $machine, 或- machine machine=$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注入中断。
分析i8258_init()函数,其主要流程如下图所示,最终创建2个i8259设备,其中一个是master,另一个是slave:
其关键函数为i8259_init_chip()函数,流程如下:
2.3 object_class_by_name()函数
object_class_by_name()函数基于TypeName初始化类,其具体实现如下:
以type_initialize("isa-i8259")函数为例,最终初始化生成的isa-i8259 TypeImpl如下,主要实现了TypeImpl的class相关的数据结构,主要是初始化类的内部状态和函数实现指针。
2.4 object_new_with_type(i8259_impl)实例化
object_new_with_type()函数用于实例化对象,其流程如下:
对于isa-i8259,由于先递归,再调用type->instance_init(),其实例化过程执行流实际是先执行祖先的instance_init(),即object_instance_init()--->device_initfn()--->isa_device_init()。
- object_instance_init()函数主要设置"type" 属性
-
device_initfn()用于设备实例初始化,主要是
- 当虚拟机创建完成后的设备init,会设置其为hotplugged状态,但此时realized = false
- 添加"realized"属性
- 将自身class的props和父类的属性加入到设备属性状态
- 将global定义的设备属性加入到设备属性状态
- 将&dev->parent_bus作为"parent_bus"属性opaque加入到device属性链表(此时还未设置parent_bus,所有使用其地址加入)
- isa_device_init()函数主要将isairq[0]和isairq[1]都初始化为-1
设备初始化完成后如下:
2.5 qdev_set_parent_bus()
将设备插入到parent_bus中。
2.6 设备实例化
在isa-i8259设备初始化完成后,最后悔调用qdev_init()函数对设备进行实例化,该函数会调用device_set_realized()对设备进行实例化。
device_set_realized()函数实现逻辑如下:
- 通过object_property_add_child()将设备对象以名称"device[]"挂到容器目录root/machine/unattached/目录,如下所示
- 调用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()
-->...
- 更新isa i8259 device vmsd相关域
- dev->realized = true,即完成设备实例化
3. isa-i8259设备中断产生机制
对于目前在x86架构上使用qemu-kvm的虚拟机,实际并没有直接使用isa-i8259设备的中断。或者是通过gsi(global system interrupt)调用,或者通过pit timer调用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。