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

2021-05-27  本文已影响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 仿真目标的文件和依赖于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总线。

上一篇下一篇

猜你喜欢

热点阅读