QEMU 构建系统架构
這份文檔旨在幫助開發者理解 QEMU 構建系統的架構。正如使用 GNU autotools 的項目一樣,QEMU 構建系統有兩個階段,第一步開發者運行 configure 腳本確定本地構建環境特性,然后執行 make 構建整個項目。與 GNU autotools 的相似之處僅此而已,因此請忘掉你已知關于它們的東西。
第一步:configure
QEMU 配置腳本是直接用 shell 寫的,且應該與任何 POSIX shell 兼容,因此它使用 #!/bin/sh。這種做法的一個重要影響是,在以 bash 為主 host 的開發平臺上避免使用 bash-isms 很重要。
與 autoconf 腳本相反,QEMU 的配置在檢查功能特性時是靜默的。只有在出錯時它才顯示輸出,或者在完成時展示最后啟用的功能特性總結。
為配置腳本添加新的檢查項通常包括如下任務:
-
初始化一個或多個包含默認狀態的變量。
理想的功能特性應該自動檢查它們是否存在,因此要努力避免通過硬編碼初始狀態來啟用或禁用,那樣強迫用戶在每次調用 configure 時傳入一個 --enable-XXX / --disable-XXX 標記。 -
為命令行參數解析器添加支持來處理功能特性 XXX 所需要的新的 --enable-XXX / --disable-XXX 標記。
-
為幫助輸出消息添加信息以報告新的功能特性標記。
-
添加代碼執行實際的功能特性檢查。如上面看到的那樣,努力完全動態地檢查啟用/禁用。
-
添加代碼在完成時的配置總結中打印功能特性狀態。
-
在完成時為 $config_host_mak 添加新的 makefile 變量。
以在 configure 中探測 gnutls (一個簡化版本)為例,我們有如下的片段:
# Initial variable stategnutls=""..snip..# Configure flag processing--disable-gnutls) gnutls="no";;--enable-gnutls) gnutls="yes";;..snip..# Help output feature messagegnutls GNUTLS cryptography support..snip..# Test for gnutlsif test "$gnutls" != "no"; thenif ! $pkg_config --exists "gnutls"; thengnutls_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"; thenfeature_not_found "gnutls" "Install gnutls devel"elsegnutls="no"fifi..snip..# Completion feature summaryecho "GNUTLS support $gnutls"..snip..# Define make variablesif test "$gnutls" = "yes" ; thenecho "CONFIG_GNUTLS=y" >> $config_host_makfi輔助函數
配置腳本提供了大量的輔助函數來幫助開發者檢查系統功能特性:
-
do_cc $ARGS...
嘗試運行系統 C 編譯器并給它傳遞 $ARGS... -
do_cxx $ARGS...
嘗試運行系統 C++ 編譯器并給它傳遞 $ARGS... -
compile_object $CFLAGS
嘗試以系統 C 編譯器使用 $CFLAGS 編譯一個測試程序。測試程序必須已經在前面寫入了稱為 $TMPC 的文件。 -
compile_prog $CFLAGS $LDFLAGS
嘗試以系統 C 編譯器使用 $CFLAGS 編譯一個測試程序,并以系統鏈接器用 $LDFLAGS 進行鏈接。測試程序必須已經在前面寫入了稱為 $TMPC 的文件。 -
has $COMMAND
確定當前環境中是否存在 $COMMAND,可以是一個 shell 內建命令或可執行二進制文件,成功時返回 0。 -
path_of $COMMAND
返回 $COMMAND 的完全限定路徑,將它打印到 stdout,成功時返回 0。 -
check_define $NAME
檢查系統 C 編譯器是否定義了宏 $NAME。 -
check_include $NAME
檢查 $NAME 頭文件對系統 C 編譯器是否可用。 -
write_c_skeleton
向 $TMPC 指向的臨時文件寫入一個最小的 C 程序 main() 函數。 -
feature_not_found $NAME $REMEDY
功能特性 $NAME 在系統上不可用時向 stderr 打印一條消息,提示用戶嘗試 $REMEDY 定位問題。 -
error_exit $MESSAGE $MORE...
向 stderr 打印 $MESSAGE,后面跟 $MORE... ,然后以非零狀態碼從配置腳本中退出。 -
query_pkg_config $ARGS...
以 $ARGS 為參數運行 pkg-config。如果 QEMU 正在執行靜態構建,則 --static 將被自動地加進 $ARGS。
第二步:makefiles
QEMU 構建系統需要使用 GNU make。
盡管源碼分布于多個子目錄下,構建系統本質上應該被認為是非遞歸的,與在 automake 中看到的通常的實踐相反。有一些 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 構建依然有效。
模塊結構
QEMU 構建系統有大量的重要輸出:
- 工具 - qemu-img,qemu-nbd,qga (guest agent),等等
- 系統模擬器 - qemu-system-$ARCH
- 用戶空間模擬器 - qemu-$ARCH
- 單元測試
源碼是高度模塊化的,分割多個文件,以便盡可能少地重復編譯所有這些組件??梢哉J為是兩個不同的文件組,那些獨立于 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.aWindows 平臺可移植性
在 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.outil-obj-y += fifo8.outil-obj-y += acl.outil-obj-y += error.o qemu-error.o當有一個目標文件需要基于主機系統的一些特性有條件地構建時,配置腳本將為條件定義一個變量。比如,在 Windows 上它將定義 $(CONFIG_POSIX) 指為 'n',而 $(CONFIG_WIN32) 值為 'y'?,F在可以在列出目標文件時使用配置變量了。比如,
util-obj-$(CONFIG_WIN32) += oslib-win32.o qemu-thread-win32.outil-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-thread-posix.o在 Windows 上被擴展為
util-obj-y += oslib-win32.o qemu-thread-win32.outil-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 所需的規則進行。它們的行為受稍后列出的動態創建的文件影響。
-
Makefile
調用 make 構建 QEMU 的所有組件的主入口點。默認的 'all' target 自然地構建每一個組件。各種工具和輔助二進制文件直接通過一個非遞歸的規則集合構建。
每個系統/用戶空間模擬器需要一些稍有不同的 make 規則/變量。因此,make 將為每個模擬器遞歸地調用。
遞歸調用將最終處理頂層的 Makefile.target 文件(稍后再說)。 -
*/Makefile.objs
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)
由于源碼分布于多個目錄下,每個文件的規則類似地都被模塊化了。因此,每個子目錄包含 .c 文件的也將常常包含一個 Makefile.objs 文件。這些文件不直接由一個遞歸的 make 調用,而是被導入到頂層的 Makefile 和/或 Makefile.target。
每個 Makefile.objs 通常只聲明一系列變量列出當前目錄下需要從源碼文件構建的 .o 文件。它們還定義任何定制的鏈接器或編譯器標志。比如在 block/Makefile.objs 中:如果 Makefile.objs 文件定義了規則,則它們都應該使用 $(obj) 作為 target 的前綴,比如:
$(obj)/generated-tcg-tracers.h: $(obj)/generated-tcg-tracers.h-timestamp -
Makefile.target
這個文件提供了構建每個獨立的系統或用戶空間模擬器 target 的入口點。每個開啟的 target 擁有它自己的子目錄。比如如果 configure 以參數 '--target-list=x86_64-softmmu' 運行,則將創建 'x86_64-softmu' 子目錄,包含一個 'Makefile',符號鏈接回 Makefile.target。
因此當調用遞歸的 '$(MAKE) -C x86_64-softmmu' 時,它將最終使用 Makefile.target 構建規則。 -
rules.mak
這個文件為調用構建工具提供了通用的輔助規則,特別是編譯器和鏈接器。這也包含了用于將源樹中所有 Makefile.objs 的變量定義合并到主 Makefile 上下文中的magic(hairy)'unnest-vars' 函數。 -
default-configs/*.mak
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...
default-configs/ 下的文件控制每個 QEMU 系統和用戶空間模擬器內置什么模擬的硬件。它們僅僅包含長長的配置變量定義列表。比如,default-configs/x86_64-softmmu.mak 有:這些文件幾乎不需要修改,除非為一個特定的系統/用戶空間模擬器啟用新的設備/硬件。
-
tests/Makefile
編譯單元測試的規則。這個文件直接被包含進頂層的 Makefile,因此這個文件中定義的任何東西將影響整個構建系統。當為測試編寫規則時需要小心,確保它們只應用于單元測試的執行/構建。 -
tests/docker/Makefile.include
Docker 測試的規則。像 tests/Makefile 一樣,這個文件直接被包含進頂層的 Makefile,這個文件中定義的任何東西將影響整個構建系統。 -
po/Makefile
從文本 .po 文件源構建和安裝二進制消息目錄的規則。它幾乎從不需要因任何原因而修改。
動態創建的文件
下面的文件是由 configure 動態生成,來控制靜態定義的 makefiles 的行為的。這使得 QEMU makefiles 無需像在 autotools 中看到的做預處理,其中 Makefile.am 生成 Makefile.in 生成 Makefile。
-
config-host.mak
當 configure 已經確定了構建主機的特性時,它將向 config-host.mak 文件寫入一個長長的變量列表。這提供了各種安裝目錄,編譯器/鏈接器標記和大量與啟用的可選功能特性有關的 CONFIG_* 變量。它被頂層的 Makefile 引入以裁剪構建輸出。
這里定義的變量適用于所有的 QEMU 構建輸出。每個模擬器目標文件可能不同的變量定義在下一個文件. . .
它還被用作一個獨立的檢查機制。如果 make 看到 configure 的修改時間戳比 config-host.mak 的更新,則 configure 將重新運行。 -
config-host.h
源碼使用 config-host.h 文件來確定啟用了那些功能特性。它是使用 scripts/create_config 程序根據 config-host.mak 的內容生成的。它提取所有的 CONFIG_ 變量,大部分 HOST_ 變量和其它一些來自于 config-host.mak 的雜項變量,以 C 預處理器宏格式化它們。 -
$TARGET-NAME/config-target.mak
TARGET-NAME 是系統或用戶空間模擬器的名字,比如 x86_64-softmmu 表示 x86_64 架構的系統模擬器。這個文件包含在每個模擬器上需要變化的變量。比如,它將指出是否為目標文件啟用 KVM 或 Xen,及鏈接目標文件所需的其它潛在的定制庫。 -
$TARGET-NAME/config-devices.mak
TARGET-NAME 是系統或用戶空間模擬器的名字。config-devices.mak 文件是 make 使用 scripts/make_device_config.sh 程序自動生成的,以 default-configs/$TARGET-NAME 文件作為輸入。 -
$TARGET-NAME/Makefile
這是 make 遞歸地構建單獨的系統或用戶空間模擬器時所使用的入口點。它僅僅是到頂層的 Makefile.target 的符號鏈接。
譯自 qemu 官方文檔 qemu/docs/build-system.txt 。
Done。
總結
以上是生活随笔為你收集整理的QEMU 构建系统架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BufferQueue 和 grallo
- 下一篇: HiKey960 开发板 android