美文网首页
QEMU学习笔记(2020-12-03)

QEMU学习笔记(2020-12-03)

作者: 胡聿泽 | 来源:发表于2021-05-27 15:23 被阅读0次

    QEMU学习笔记

    1 QEMU构建系统架构

    1.1 Makefiles

    QEMU 构建系统需要使用 GNU make。

    QEMU 当前支持 VPATH 和非 VPATH 构建,因此有三种通用方式调用 configure 并执行构建。

    VPATH,完全在 QEMU 源码树之外构建产品

    $ cd ../
    $ mkdir build
    $ cd build
    $ ../qemu/configure
    $ make
    

    VPATH,在 QEMU 源码树的一个子目录中构建产品

    $ mkdir build
    $ cd build
    $ ../configure
    $ make
    

    非 VPATH,在任何地方构建产品

    $ ./configure
    $ make
    

    QEMU 的维护者通常建议开发者使用 VPATH 构建。QEMU 的补丁期待确保 VPATH 构建依然有效。

    1.2 模块结构

    QEMU 构建系统有大量的重要输出:

    • 工具 - qemu-img,qemu-nbd,qga (guest agent),等等
    • 系统模拟器 - qemu-system-$ARCH
    • 用户空间模拟器 - qemu-$ARCH
    • 单元测试

    源码是高度模块化的,分割多个文件,以便尽可能少地重复编译所有这些组件。可以认为是两个不同的文件组,包括独立于 QEMU 仿真目标的文件和依赖于QEMU 仿真目标的文件组。

    独立于仿真目标的文件集合中是各种通用辅助代码,比如错误处理基础设施,标准数据结构,平台移植性封装函数,等等。这些代码可以只被编译一次,而把它们的 .o 文件链接到所有的输出二进制文件。

    依赖于仿真目标的文件集合中是 CPU 模拟,设备模拟和许多胶水代码。这有时还不得不编译多次,为每个目标编译一次。

    所有二进制文件都用到的实用代码被编译为一个称为 libqemuutil.a 静态包,它会被链接进所有的二进制文件。为了提供只有一部分二进制文件需要的钩子,libqemuutil.a 中的代码可能依赖于其它不完全由 QEMU 二进制实现的函数。为了处理这种情况,还有另一个称为 libqemustub.a 的库,它为所有这些函数提供了 dummy stubs。如果没有真正的实现,则它们将被延迟链接进二进制中。以这种方式,libqemustub.a 静态库可以被看作一个弱符号概念的可移植实现。所有的二进制文件应该同时链接 libqemuutil.a 和 libqemustub.a。比如

     qemu-img$(EXESUF): qemu-img.o ..snippet.. libqemuutil.a libqemustub.a
    

    1.3 目标变量命名

    QEMU 用约定变量来列出不同的目标文件组的。它们的命名约定为 $PREFIX-obj-y。比如,libqemuutil.a 文件将与变量 util-obj-y 列出的所有目标文件链接。因此,比如,util/Makefile.obj 将包含一系列看起来像这样的定义:

      util-obj-y += bitmap.o bitops.o hbitmap.o
      util-obj-y += fifo8.o
      util-obj-y += acl.o
      util-obj-y += error.o qemu-error.o
    

    当有一个目标文件需要基于主机系统的一些特性有条件地构建时,配置脚本将条件定义一个变量。比如,在 Windows 上它将定义 $(CONFIG_POSIX) 值为 'n',而 $(CONFIG_WIN32) 值为 'y'。现在可以在列出目标文件时使用配置变量了。比如,

      util-obj-$(CONFIG_WIN32) += oslib-win32.o qemu-thread-win32.o
      util-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-thread-posix.o
    

    在 Windows 上被扩展为

      util-obj-y += oslib-win32.o qemu-thread-win32.o
      util-obj-n += oslib-posix.o qemu-thread-posix.o
    

    由于 libqemutil.a 被链接进 $(util-obj-y) ,在 Windows 平台构建中,$(util-obj-n) 中列出的 POSIX 特有文件被忽略。

    2 模块的注册与加载

    2.1 模块的注册

    QEMU 4.2.0 版本的入口在 vl.cmain函数中,其中在vl.c的 2885 行是module_call_init(MODULE_INIT_QOM);

    module_call_initinclude/qemu/module.h中声明,在util/module.c中定义,其作用是遍历init_type_list[MODULE_INIT_QOM]调用所有已注册 QOM(QEMU Object Module) 模块的init函数,完成 QOM 模块的初始化。

    QOM 模块的注册在每个模块文件末尾的type_init中进行,type_init通过include/qemu/module.h#module_init宏定义的__attribute__((constructor))将注册函数转换成构造函数,在运行main入口函数之前完成注册。

    hw/virtio/virtio-blk-pci.c为例,module_call_init会调用init,而init实际指向virtio_blk_pci_register, virtio_blk_pci_register最终会将设备对应的TypeImpl注册到哈希表中(以TypeInfo的name属性为索引)

    2.2 参数解析

    两次循环中解析argv,将解析结果保存到QemuOpts链表中

    2.3 模块加载

    1. 在main函数中会遍历指定的-device参数,然后调用device_init_func来做设备的初始化。

    2. 会依次调用device_init_func-> qdev_device_add -> object_new -> object_initialize_with_type。主要的初始化工作都是在object_initialize_with_type中完成的。

    3. 首先会调用type_initialize完成类的初始化。在类的初始化中会设置类的realize回调函数为virtio_balloon_pci_realize。该函数在做类对象的实例化的时候会调用。

    4. 之后会调用object_init_with_type做类对象的初始化。会递归从父对象开始执行instance_init。TYPE_DEVICE的instance_init函数为device_initfn。该函数调用执行了object_property_add_bool增加了三个属性。其中realized属性是当设置了对象真正创建的时候调用的。其set回调函数设置为device_set_realized。任何一个设备创建的时候都会调用该函数。

    image

    5. 设备类和对象实例初始化完成后会回到qdev_device_add函数,接下来就会进行具体的设备实现过程。

    6. 调用object_property_set_bool将realized属性设置为true,从而会调用刚才设置的回调函数device_set_realized。

    7. 会首先调用DeviceClass的realize函数即virtio_pci_dc_realize。该函数是在virtio_pci_class_init中设置的。

    image

    8. 父类的realize函数会一次调用子类的realize函数。接着会调用PCIDeviceClass的realize函数即pci_qdev_realize。

    9. 然后依次调用VirtioPCIClass、VirtioBalloonPCIClass的realize函数。从而实现了VirtioBalloonPCI设备的创建。最后还需要创建VirtioBalloon设备,将其挂载到PCI总线上。

    10. 即依次调用VirtioDeviceClass、VirtioBalloonDeviceClass的realize函数。VirtioBalloon设备的具体实现就是在函数virtio_balloon_device_realize中。

    整个流程如下图所示,类之间的调用流则如类继承关系图中红线所示。

    image

    在virtio_balloon_device_realize中,调用virtio_add_queue为设备添加了三个virtqueue。其中两个的回调处理函数为virtio_ballon_handle_output,另一个是virtio_balloon_receive_stats。从而构建了从设备的输入输出通道,以及当收到输入输出信息时调用的回调处理函数。

    image

    至此设备创建完成,但是还差最后一步将设备挂载到virtio总线上去。该步骤是在函数virtio_device_realize中实现的。该函数调用vdc->realize创建了具体设备后,会调用virtio_bus_device_plugged,该函数的作用就是将virtio设备插入到virtio总线。

    相关文章

      网友评论

          本文标题:QEMU学习笔记(2020-12-03)

          本文链接:https://www.haomeiwen.com/subject/akkvwktx.html