QEMU 构建系统架构

2017-07-20  本文已影响264人  hanpfei

这份文档旨在帮助开发者理解 QEMU 构建系统的架构。正如使用 GNU autotools 的项目一样,QEMU 构建系统有两个阶段,第一步开发者运行 configure 脚本确定本地构建环境特性,然后执行 make 构建整个项目。与 GNU autotools 的相似之处仅此而已,因此请忘掉你已知关于它们的东西。

第一步:configure

QEMU 配置脚本是直接用 shell 写的,且应该与任何 POSIX shell 兼容,因此它使用 #!/bin/sh。这种做法的一个重要影响是,在以 bash 为主 host 的开发平台上避免使用 bash-isms 很重要。

与 autoconf 脚本相反,QEMU 的配置在检查功能特性时是静默的。只有在出错时它才显示输出,或者在完成时展示最后启用的功能特性总结。

为配置脚本添加新的检查项通常包括如下任务:

以在 configure 中探测 gnutls (一个简化版本)为例,我们有如下的片段:

  # Initial variable state
  gnutls=""

  ..snip..

  # Configure flag processing
  --disable-gnutls) gnutls="no"
  ;;
  --enable-gnutls) gnutls="yes"
  ;;

  ..snip..

  # Help output feature message
  gnutls          GNUTLS cryptography support

  ..snip..

  # Test for gnutls
  if test "$gnutls" != "no"; then
     if ! $pkg_config --exists "gnutls"; then
        gnutls_cflags=`$pkg_config --cflags gnutls`
        gnutls_libs=`$pkg_config --libs gnutls`
        libs_softmmu="$gnutls_libs $libs_softmmu"
        libs_tools="$gnutls_libs $libs_tools"
        QEMU_CFLAGS="$QEMU_CFLAGS $gnutls_cflags"
        gnutls="yes"
     elif test "$gnutls" = "yes"; then
        feature_not_found "gnutls" "Install gnutls devel"
     else
        gnutls="no"
     fi
  fi

  ..snip..

  # Completion feature summary
  echo "GNUTLS support    $gnutls"

  ..snip..

  # Define make variables
  if test "$gnutls" = "yes" ; then
     echo "CONFIG_GNUTLS=y" >> $config_host_mak
  fi

辅助函数

配置脚本提供了大量的辅助函数来帮助开发者检查系统功能特性:

第二步:makefiles

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

尽管源码分布于多个子目录下,构建系统本质上应该被认为是非递归的,与在 automake 中看到的通常的实践相反。有一些 make 的递归调用,但这与要构建的东西有关,而不是源码目录的结构。

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

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

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

模块结构

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 ..snip.. libqemuutil.a libqemustub.a

Windows 平台可移植性

在 Windows 平台上,所有的二进制文件都有后缀 '.exe',因此所有创建二进制文件的 Makefile 规则都必须在二进制文件名中包含 $(EXESUF) 变量。比如,

 qemu-img$(EXESUF): qemu-img.o ..snip..

这在 Windows 平台上展开为 '.exe',或者在其它平台上为 ''。

系统模拟器二进制文件的另一重复杂性是需要生成两个单独的二进制文件。主二进制文件(例如 qemu-system-x86_64.exe)与 Windows 控制台运行时子系统链接。这些预期将从命令提示符窗口运行,因此,将 stderr 打印到启动它们的控制台。

生成的第二个二进制文件在它们的名字的最后有一个 'w' (比如 qemu-system-x86_64w.exe ),它们与 Windows 图形运行时子系统链接。这些预计将直接从桌面运行,且将打开一个专门的终端窗口用于 stderr 输出。

Makefile.target 将首先为图形子系统生成二进制文件,然后使用 objcopy 重新与终端子系统链接生成第二个二进制文件。

目标变量命名

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 特有文件被忽略。

CFLAGS / LDFLAGS / LIBS 处理

有许多不同的二进制文件为了不同目的而构建,其中一些甚至可能是通过git子模块拉入的第三方库。因此通常在 QEMU 中要避免使用全局的 CFLAGS 变量,由于它将应用于太多的构建目标。

所有的 QEMU 代码(比如,除了 GIT子模块工程)需要的标记都被放进了 $(QEMU_CFLAGS) 变量。对于链接器标志,有时会使用 $(LIBS) 变量,但是优先选择几个更有针对性的变量。$(libs_softmmu) 用于必须链接到系统模拟器的库,$(LIBS_TOOLS) 用于像 qemu-img,qemu-nbd,等等的工具,$(LIBS_QGA) 用于 QEMU guest agent。当前没有专门用于用户空间模拟器的变量作为全局 $(LIBS),或者下面显示的更有针对性的变量就足够了。

除了这些变量,可以针对各个源代码文件提供 cflags 和 libs,通过以 $FILENAME-cflags 和 $FILENAME-libs 的形式定义变量。比如,curl 块驱动需要链接 libcurl 库,于是 block/Makefile 定义了一些变量:

  curl.o-cflags      := $(CURL_CFLAGS)
  curl.o-libs        := $(CURL_LIBS)

这两个变量的影响范围有一点不同。当链接任何包含 curl.o 目标文件的二进制文件时 libs 都会被用到,而 cflags 只有在编译 curl.c 时被用到。

静态定义的文件

下面的重要文件是在源码树中静态定义的,以构建 QEMU 所需的规则进行。它们的行为受稍后列出的动态创建的文件影响。

  block-obj-$(CONFIG_LIBISCSI) += iscsi.o
  block-obj-$(CONFIG_CURL) += curl.o

  ..snip...

  iscsi.o-cflags     := $(LIBISCSI_CFLAGS)
  iscsi.o-libs       := $(LIBISCSI_LIBS)
  curl.o-cflags      := $(CURL_CFLAGS)
  curl.o-libs        := $(CURL_LIBS)

如果 Makefile.objs 文件定义了规则,则它们都应该使用 $(obj) 作为 target 的前缀,比如:

  $(obj)/generated-tcg-tracers.h: $(obj)/generated-tcg-tracers.h-timestamp
  include pci.mak
  include sound.mak
  include usb.mak
  CONFIG_QXL=$(CONFIG_SPICE)
  CONFIG_VGA_ISA=y
  CONFIG_VGA_CIRRUS=y
  CONFIG_VMWARE_VGA=y
  CONFIG_VIRTIO_VGA=y
  ...snip...

这些文件几乎不需要修改,除非为一个特定的系统/用户空间模拟器启用新的设备/硬件。

动态创建的文件

下面的文件是由 configure 动态生成,来控制静态定义的 makefiles 的行为的。这使得 QEMU makefiles 无需像在 autotools 中看到的做预处理,其中 Makefile.am 生成 Makefile.in 生成 Makefile。

译自 qemu 官方文档 qemu/docs/build-system.txt 。

Done。

上一篇下一篇

猜你喜欢

热点阅读