Skip to the content.

qom property

qom property 算是 qom 中比较复杂的部分,需要单独说明下,

关于 QEMU 中 property, 可以参考 Paolo Bonzini 在 2014 KVM Forum 上的总结

All properties are accessed through visitors:

基本概念

class MachineState {
public:
    bool usb;  // 每个实例一份

    bool get_usb() const { return usb; }
    void set_usb(bool value) { usb = value; }

    // 概念上的属性描述符:类级共享
    static PropertyDescriptor usb_property;
};

也就是说 QEMU 实际上分成两层:

MachineClass/ObjectClass
  └── properties["usb"]
        ├── type = "bool"
        ├── get = machine_get_usb
        └── set = machine_set_usb
                 │
                 ▼
MachineState 实例
  └── ms->usb                每个实例独立

所有的 instance 共享相同的 properties[“usb”] ,但是 但是这些 instance 的 properties[“usb”] 函数作用的成员是 各自的 instance 的

class Machine:
    @property
    def usb(self):
        return self._usb

    @usb.setter
    def usb(self, value):
        self._usb = value

这里:

结合下面的 backtrace 可以分析出来, 即使 property 是 class 的,所有的 instance 共享,但是依旧可以设置到 object 的属性上。

- main
  - qemu_init
    - qmp_x_exit_preconfig
      - qmp_x_exit_preconfig
        - qemu_init_board
          - machine_run_board_init
            - pc_init1
              - x86_cpus_init
                - x86_cpu_new
                  - qdev_realize
                    - object_property_set_bool
                      - object_property_set_qobject
                        - object_property_set
                          - property_set_bool
                            - device_set_realized
                              - x86_cpu_realizefn
                                - x86_cpu_apic_create
                                  - object_new_with_type
                                    - object_initialize_with_type
                                      - object_class_property_init_all
                                        - object_property_init_defval
                                          - set_uint8

分类

QOM API property 描述符范围 通常的值范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ object_class_property_add_bool(klass, …) 当前 class 及其派生 class 的全部实例 getter/setter 决定;通常是实例成员 ────────────────────────────────────────────── ────────────────────────────────────── ──────────────────────────────────────── object_property_add_bool(obj, …) 仅指定的那个 object 通常是该实例成员 ────────────────────────────────────────────── ────────────────────────────────────── ──────────────────────────────────────── object_class_static_property_add_uint*_ptr() 当前 class 及其派生 class 的全部实例 明确指向实例外的全局变量,所有实例共享

class property

这个是最常见的

static class property

真正类似 C++ static 成员的例子在 hw/riscv/spike.c 的 spike_machine_class_init():

object_class_static_property_add_uint8_ptr( oc, “signature-granularity”, &line_size, OBJ_PROP_FLAG_WRITE);

line_size 定义在 hw/char/riscv_htif.c:

uint8_t line_size = 16;

这才近似:

class SpikeMachine {
    static uint8_t line_size;
};

所有 Spike machine 实例访问 signature-granularity,最终操作的都是同一个全局 line_size。

目前整个 QEMU 只有这一个属性是这个操作方法。

instance property

因为有些属性并不属于该类型的所有实例。例如 hw/core/machine.c 的 machine_initfn():

if (mc->nvdimm_supported) {
    object_property_add_bool(obj, "nvdimm", ...);
}

所以 QOM 实际采用的是:

案例

memory_region_initfn 中定义的属性不必要是所有的 class 都需要

object_class_property_add() 到 2016 年才由 commit 16bf7f522a2f 引入。 因此当时只能在 memory_region_initfn() 中调用 object_property_add()。

因此现在这两个属性完全可以改成:

static void memory_region_class_init(ObjectClass *oc, const void *data)
{
    object_class_property_add(oc, "priority", "uint32",
                              memory_region_get_priority,
                              NULL, NULL, NULL);
    object_class_property_add(oc, "size", "uint64",
                              memory_region_get_size,
                              NULL, NULL, NULL);
}

然后在 TypeInfo 中增加:

  .class_init = memory_region_class_init,

这样语义基本不变,但所有 MemoryRegion 实例共享属性描述,可以减少每个实例的属性分配。

同一个 memory_region_initfn() 中的其他属性不能如此简单地迁移:

    op = object_property_add(OBJECT(mr), "container",
                             "link<" TYPE_MEMORY_REGION ">",
                             memory_region_get_container,
                             NULL, /* memory_region_set_container */
                             NULL, NULL);
    op->resolve = memory_region_resolve_container;

property 内部实现

struct ObjectClass
{
    GHashTable *properties;
};

struct Object
{
    GHashTable *properties;
};

当查询 ObjectProperty 的时候,这会同时查询两个 ObjectClass::properties 和 Object::properties 中的内容:

ObjectProperty *object_property_find(Object *obj, const char *name)
{
    ObjectProperty *prop;
    ObjectClass *klass = object_get_class(obj);

    prop = object_class_property_find(klass, name);
    if (prop) {
        return prop;
    }

    return g_hash_table_lookup(obj->properties, name);
}

如何添加 Property

device_class_set_props

例如定义到所有的 PCIDevice 上的属性

static Property pci_props[] = {
		// ...
    DEFINE_PROP_BIT("multifunction", PCIDevice, cap_present,
                    QEMU_PCI_CAP_MULTIFUNCTION_BITNR, false),
		// ...
    DEFINE_PROP_END_OF_LIST()
};

将 macro 展开之后:

static Property pci_props[] = {
    {.name = ("multifunction"),
     .info = &(qdev_prop_bit),
     .offset = offsetof(PCIDevice, cap_present) +
               type_check(uint32_t, typeof_field(PCIDevice, cap_present)),
     .bitnr = (QEMU_PCI_CAP_MULTIFUNCTION_BITNR),
     .set_default = true,
     .defval.u = (bool)false},
    {}};

object_class_property_add

例如 kvm_accel_class_init 中

    object_class_property_add(oc, "kernel-irqchip", "on|off|split",
        NULL, kvm_set_kernel_irqchip,
        NULL, NULL);

QOM composition tree

property 中间不仅仅可以存储 str / int 之类基本类型,还可以用于存储 Object 。 通过 link 和 child 类型的 property 可以构建出来 QOM tree

在 QEMU monitor 中使用 info qom-tree 可以查看 QOM tree, 全部的内容列举到了这里,下面只是一部分。

/machine (pc-i440fx-6.1-machine)
  /fw_cfg (fw_cfg_io)
    /\x2from@etc\x2facpi\x2frsdp[0] (memory-region)
    /\x2from@etc\x2facpi\x2ftables[0] (memory-region)
    /\x2from@etc\x2ftable-loader[0] (memory-region)
    /fwcfg.dma[0] (memory-region)
    /fwcfg[0] (memory-region)
  /i440fx (i440FX-pcihost)
    /ioapic (ioapic)
      /ioapic[0] (memory-region)
      /unnamed-gpio-in[0] (irq)
  /unattached (container)
    /device[0] (qemu64-x86_64-cpu)
      /lapic (apic)
        /apic-msi[0] (memory-region)
      /memory[0] (memory-region)
      /memory[1] (memory-region)
      /smram[0] (memory-region)

然后就可以通过路径直接获取到一个 object 了,例如:

MemoryRegion *smram = (MemoryRegion *) object_resolve_path("/machine/smram", NULL);

child

使用上面的 qom tree 作为例子说明。

回顾一下刚才的例子:

MemoryRegion *smram = (MemoryRegion *) object_resolve_path("/machine/smram", NULL);

实际上,我们发现访问 smram 正确的路径应该是 “/machine/unattached/device[0]/smram[0]” 的

路径解析的一般过程为:

在 i440fx_init 中 object_property_add_const_link(qdev_get_machine(), "smram", OBJECT(&f->smram)); 这导致解析路径到 smram 之后,调用到 object_resolve_link_property, 最后返回的是 OBJECT(&f->smram)

实际上,在 QEMU 中 link 作用还可以和 object_property_add_str 类似,只是将 string 替换为 * object 例如在 pic 控制器中的:

alias

alias 可以根据让两个名称找到同一个 property

比如在 x86_cpu_initfn 中间的操作:

    object_property_add_alias(obj, "sse3", obj, "pni", &error_abort);
    object_property_add_alias(obj, "pclmuldq", obj, "pclmulqdq", &error_abort);
    object_property_add_alias(obj, "sse4-1", obj, "sse4.1", &error_abort);
    object_property_add_alias(obj, "sse4-2", obj, "sse4.2", &error_abort);

在比如 pc_machine_initfn 中:

object_property_add_alias(OBJECT(pcms), "pcspk-audiodev", OBJECT(pcms->pcspk), "audiodev");

实现对于默认赋值的修改

PCIDevice *pci_new_multifunction(int devfn, bool multifunction,
                                 const char *name)
{
    DeviceState *dev;

    dev = qdev_new(name);
    qdev_prop_set_int32(dev, "addr", devfn);
    qdev_prop_set_bit(dev, "multifunction", multifunction);
    return PCI_DEVICE(dev);
}

通过 qdev_prop_set_bit 之类的最后可以设置到 pci_props 描述的 PCIDevice 上的成员上。

这个真的很烦,让代码索引工具失效, hw/isa/piix.c 中有这个

        qdev_prop_set_bit(DEVICE(&d->pm), "smm-enabled", d->smm_enabled);

但是通过 struct PIIX4PMState::smm_enabled 是找不到的,应该不是 ccls 的问题

此外,真的有办法通过 cmdline 给 bit 设置上 bit 吗?

但是,这个地方,就绝对不可能发现吧:

        object_property_set_bool(OBJECT(pci_dev), "smm-enabled",
                                 x86_machine_is_smm_enabled(x86ms),
                                 &error_abort);
- main
  - qemu_init
    - qmp_x_exit_preconfig
      - qemu_init_board
        - machine_run_board_init
          - pc_init1
- main
  - qemu_init
    - qmp_x_exit_preconfig
      - qemu_init_board
        - machine_run_board_init
          - pc_init1
            - pci_realize_and_unref
              - qdev_realize_and_unref
                - qdev_realize
                  - object_property_set_bool
                    - object_property_set_qobject
                      - object_property_set
                        - property_set_bool
                          - device_set_realized
                            - pci_qdev_realize
                              - pci_piix_realize

发现 pci_piix_realize 的调用来自于这里:

        pci_dev = pci_new_multifunction(-1, pcms->south_bridge);
        object_property_set_bool(OBJECT(pci_dev), "has-usb",
                                 machine_usb(machine), &error_abort);
        object_property_set_bool(OBJECT(pci_dev), "has-acpi",
                                 x86_machine_is_acpi_enabled(x86ms),
                                 &error_abort);
        object_property_set_bool(OBJECT(pci_dev), "has-pic", false,
                                 &error_abort);
        object_property_set_bool(OBJECT(pci_dev), "has-pit", false,
                                 &error_abort);
        qdev_prop_set_uint32(DEVICE(pci_dev), "smb_io_base", 0xb100);
        object_property_set_bool(OBJECT(pci_dev), "smm-enabled",
                                 x86_machine_is_smm_enabled(x86ms),
                                 &error_abort);
        dev = DEVICE(pci_dev);
        for (i = 0; i < ISA_NUM_IRQS; i++) {
            qdev_connect_gpio_out_named(dev, "isa-irqs", i, x86ms->gsi[i]);
        }
        pci_realize_and_unref(pci_dev, pcms->pcibus, &error_fatal);

所以,我猜测,的确是没有办法实现 cmdline 来控制 piix 的

GlobalProperty

一种通过 -global 选项来在启动的时候修改 object property 的方式,几乎没有人使用吧!

通过 Man qemu-system(1) 中找到的:

-global driver.prop=value
-global driver=driver,property=property,value=value
   Set default value of driver's property prop to value, e.g.:

           qemu-system-x86_64 -global ide-hd.physical_block_size=4096 disk-image.img

   In particular, you can use this to set driver properties for devices which are created automatically by the
   machine model. To create a device which is not created automatically and set properties on it, use -device.

   -global driver.prop=value is shorthand for -global driver=driver,property=prop,value=value.  The longhand
   syntax works even when driver contains a dot.

此外添加 GlobalProperty 是在 pc.c 中的:

GlobalProperty pc_compat_6_0[] = {
    { "qemu64" "-" TYPE_X86_CPU, "family", "6" },
    { "qemu64" "-" TYPE_X86_CPU, "model", "6" },
    { "qemu64" "-" TYPE_X86_CPU, "stepping", "3" },
    { TYPE_X86_CPU, "x-vendor-cpuid-only", "off" },
    { "ICH9-LPC", "acpi-pci-hotplug-with-bridge-support", "off" },
};

构建的 GlobalProperty 主要通过 qdev_prop_register_global 添加到 global_props 上

使用 object_apply_global_props 来将 global_props 中存储的 property apply 到特定的 object 上。

object_apply_global_props 主要的两个调用位置:

经典案例

-cpu host,tsc-frequency=1000000000

由于是在 cpu 这个 class 添加 property :

    object_class_property_add(oc, "tsc-frequency", "int",
                              x86_cpuid_get_tsc_freq,
                              x86_cpuid_set_tsc_freq, NULL, NULL);

machine.usb

static bool machine_get_usb(Object *obj, Error **errp)
{
    MachineState *ms = MACHINE(obj);

    return ms->usb;
}

static void machine_set_usb(Object *obj, bool value, Error **errp)
{
    MachineState *ms = MACHINE(obj);

    ms->usb = value;
    ms->usb_disabled = !value;
}
    object_class_property_add_bool(oc, "usb",
        machine_get_usb, machine_set_usb);

想要修改 MachineState.usb 可以用如下四个方法:

启动参数

    qemu-system-x86_64 -machine q35,usb=off
    qemu-system-x86_64 -machine q35,usb=on

hmp

    (qemu) qom-set /machine usb off
    (qemu) qom-get /machine usb

qmp


    { "execute": "qom-set",
      "arguments": { "path": "/machine", "property": "usb", "value": false } }

或者用 qmp shell

  (QEMU) qom-set path=/machine property=usb value=false
  {"return": {}}

内部 C 代码

    Error *err = NULL;
    object_property_set_bool(OBJECT(machine), "usb", false, &err);

更底层的通用接口还有 object_property_set()(Visitor)和 object_property_set_qobject(); 命令行 -machine …,usb=… 最终也通过 QOM 属性设置路径到达此 setter。

TODO

  1. 这里我们看到了 qmp 和 hmp 是走的不同的路径,为什么这么设计
    1. 如果这样,意味着 qmp 和 hmp 可以同时对外服务,例如 docs/qemu/migration/hotplug.md 终端
      • 我感觉可以找到更多的有趣的反例来,就是现在很多代码都是假设qmp 的执行是一条一条的

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。