歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> QEMU 代碼分析:BIOS 的加載過程

QEMU 代碼分析:BIOS 的加載過程

日期:2017/2/28 14:25:02   编辑:Linux教程

QEMU 是一個廣泛使用的開源計算機仿真器和虛擬機,它提供了虛擬機硬件的虛擬化功能,其使用的某些特定硬件的固件則由一些開源項目提供。本文將介紹 QEMU 代碼中使用到的 BIOS,通過分析 QEMU 代碼,講解 BIOS 是如何加載到虛擬機的物理內存。

QEMU 中使用 BIOS 簡介

BIOS 提供主板或者顯卡的固件信息以及基本輸入輸出功能,QEMU 使用的是一些開源的項目,如 Bochs、openBIOS 等。QEMU 中使用到的 BIOS 以及固件一部分以二進制文件的形式保存在源碼樹的 pc-bios 目錄下。pc-bios 目錄裡包含了 QEMU 使用到的固件,還有一些 BIOS 以 git 源代碼子模塊的形式保存在 QEMU 的源碼倉庫中,當編譯 QEMU 程序的時候,也同時編譯出這些 BIOS 或者固件的二進制文件。QEMU 支持多種啟動方式,比如說 efi、pxe 等, 都包含在該目錄下,這些都需要特定 BIOS 的支持。

清單 1. QEMU 源碼樹中的 BIOS 文件
$ ls pc-bios/
acpi-dsdt.aml efi-rtl8139.rom openbios-ppc pxe-e1000.rom qemu_logo_no_text.svg slof.bin bamboo.dtb 
efi-virtio.rom openbios-sparc32 pxe-eepro100.rom qemu-nsis.bmp spapr-rtas bamboo.dts keymaps 
openbios-sparc64 pxe-ne2k_pci.rom qemu-nsis.ico spapr-rtas.bin bios.bin kvmvapic.bin optionrom 
pxe-pcnet.rom vgabios.bin efi-e1000.rom linuxboot.bin  palcode-clipper pxe-rtl8139.rom 
 s390-ccwvgabios-cirrus.bin efi-eepro100.rom petalogix-ml605.dtb  pxe-virtio.rom  s390-ccw.img  
vgabios-qxl.bin efi-ne2k_pci.rom  multiboot.bin    petalogix-s3adsp1800.dtb  q35-acpi-dsdt.aml  
s390-zipl.rom vgabios-stdvga.bin  efi-pcnet.rom ohw.diff ppc_rom.bin qemu-icon.bmp sgabios.bin 
 vgabios-vmware.bin
清單 2. QEMU 源碼樹以子模塊方式保存的 BIOS 代碼
-bash-4.1$ cat .gitmodules
[submodule "roms/vgabios"]
        path = roms/vgabios
        url = git://git.qemu.org/vgabios.git/
[submodule "roms/seabios"]
        path = roms/seabios
        url = git://git.qemu.org/seabios.git/
[submodule "roms/SLOF"]
        path = roms/SLOF
        url = git://git.qemu.org/SLOF.git
[submodule "roms/ipxe"]
        path = roms/ipxe
        url = git://git.qemu.org/ipxe.git
[submodule "roms/openbios"]
        path = roms/openbios
        url = git://git.qemu.org/openbios.git
[submodule "roms/qemu-palcode"]
        path = roms/qemu-palcode
        url = git://github.com/rth7680/qemu-palcode.git
[submodule "roms/sgabios"]
        path = roms/sgabios
        url = git://git.qemu.org/sgabios.git
[submodule "pixman"]
        path = pixman
        url = git://anongit.freedesktop.org/pixman
[submodule "dtc"]
        path = dtc
         url = git://git.qemu.org/dtc.git

當我們從源代碼編譯 QEMU 時候,QEMU 的 Makefile 會將這些二進制文件拷貝到 QEMU 的數據文件目錄中。
清單 3. QEMU 的 Makefile 中關於 BIOS 的拷貝操作:
ifneq ($(BLOBS),)
        set -e; for x in $(BLOBS); do \
                $(INSTALL_DATA) $(SRC_PATH)/pc-bios/$$x "$(DESTDIR)$(qemu_datadir)"; \
        done

QEMU 加載 BIOS 過程分析

當 QEMU 用戶空間進程開始啟動時,QEMU 進程會根據所傳遞的參數以及當前宿主機平台類型,自動加載適當的 BIOS 固件。 QEMU 進程啟動初始階段,會通過 module_call_init 函數調用 qemu_register_machine 注冊該平台支持的全部機器類型,接著調用 find_default_machine 選擇一個默認的機型進行初始化。 以最新的 QEMU 代碼(1.7.0)的 x86_64 平台為例,支持的機器類型有:

清單 4. 1.7.0 版本 x86_64 QEMU 中支持的類型
pc-q35-1.7 pc-q35-1.6 pc-q35-1.5 pc-q35-1.4 pc-i440fx-1.7 pc-i440fx-1.6 pc-i440fx-1.5
pc-i440fx-1.4 pc-1.3 pc-1.2 pc-1.1 pc-1.0 pc-0.15 pc-0.14
pc-0.13    pc-0.12    pc-0.11    pc-0.10    isapc

最新代碼中使用的默認機型為 pc-i440fx-1.7,使用的 BIOS 文件為:

pc-bios/bios.bin
Default machine name : pc-i440fx-1.7
bios_name = bios.bin

pc-i440fx-1.7 解釋為 QEMU 模擬的是 INTEL 的 i440fx 硬件芯片組,1.7 為 QEMU 的版本號。找到默認機器之後,為其初始化物理內存,QEMU 首先申請一塊內存空間用於模擬虛擬機的物理內存空間,申請完好內存之後,根據不同平台或者啟動 QEMU 進程的參數,為虛擬機的物理內存初始化。具體函數調用過程見圖 1。

圖 1. QEMU 硬件初始化函數調用流程圖:

在 QEMU 中,整個物理內存以一個結構體 struct MemoryRegion 表示,具體定義見清單 5。

清單 5. QEMU 中 MemoryRegion 結構體
struct MemoryRegion {
    /* All fields are private - violators will be prosecuted */
    const MemoryRegionOps *ops;
    const MemoryRegionIOMMUOps *iommu_ops;
    void *opaque;
    struct Object *owner;
    MemoryRegion *parent;
    Int128 size;
    hwaddr addr;
    void (*destructor)(MemoryRegion *mr);
    ram_addr_t ram_addr;
    bool subpage;
    bool terminates;
    bool romd_mode;
    bool ram;
    bool readonly; /* For RAM regions */
    bool enabled;
    bool rom_device;
    bool warning_printed; /* For reservations */
    bool flush_coalesced_mmio;
    MemoryRegion *alias;
    hwaddr alias_offset;
    unsigned priority;
    bool may_overlap;
    QTAILQ_HEAD(subregions, MemoryRegion) subregions;
    QTAILQ_ENTRY(MemoryRegion) subregions_link;
    QTAILQ_HEAD(coalesced_ranges, CoalescedMemoryRange) subregions_link;
    const char *name;
    uint8_t dirty_log_mask;
    unsigned ioeventfd_nb;
    MemoryRegionIoeventfd *ioeventfds;
    NotifierList iommu_notify;
};

每一個 MemoryRegion 代表一塊內存區域。仔細觀察 MemoryRegion 的成員函數,它包含一個 Object 的成員函數用於指向它的所有者,以及一個 MemoryRegion 成員用於指向他的父節點(有點類似鏈表)。另外還有三個尾隊列(QTAILQ) subregions, subregions_link, subregions_link。 也就是說,一個 MemoryRegion 可以包含多個內存區,根據不同的參數區分該內存域的功能。 在使用 MemoryRegion 之前要先為其分配內存空間並調用 memory_region_init 做必要的初始化。BIOS 也是通過一個 MemoryRegion 結構指示的。它的 MemoryRegion.name 被設置為"pc.bios", size 設置為 BIOS 文件的大小(65536 的整數倍)。接著掉用 rom_add_file_fixed 將其 BIOS 文件加載到一個全局的 rom 隊列中。

最後,回到 old_pc_system_rom_init 函數中,將 BIOS 映射到內存的最上方的地址空間。

清單 6. old_pc_system_rom_init 函數中將 BIOS 映射到物理內存空間的代碼:
hw/i386/pc_sysfw.c :
    memory_region_add_subregion(rom_memory,
        (uint32_t)(-bios_size) bios);

(uint32_t)(-bios_size) 是一個 32 位無符號數字,所以-bios_size 對應的地址就是 FFFFFFFF 減掉 bios_size 的大小。 bios size 大小為 ./pc-bios/bios.bin = 131072 (128KB)字節,十六進制表示為 0x20000,所以 bios 在內存中的位置為 bios position = fffe0000,bios 在內存中的位置就是 0xfffdffff~0xffffffff 現在 BIOS 已經加在到虛擬機的物理內存地址空間中了。

最後 QEMU 調用 CPU 重置函數重置 VCPU 的寄存器值 IP=0x0000fff0, CS=0xf000, CS.BASE= 0xffff0000,CS.LIMIT=0xffff. 指令從 0xfffffff0 開始執行,正好是 ROM 程序的開始位置。虛擬機就找到了 BIOS 的入口。

小結

作者通過閱讀 QEMU 程序的源代碼,詳細介紹了 QEMU 中使用到的 BIOS 文件,QEMU 中物理內存的表示方法,以及 QEMU 是如何一步步將 BIOS 的二進制載入到通過 QEMU 創建的虛擬機中的內存的過程。

參考 http://wiki.qemu.org/Main_Page:關於 QEMU 項目的介紹。

參考 git://git.qemu.org/qemu.git 中 QEMU 的源代碼。

Ubuntu 12.04之找不到Qemu命令 http://www.linuxidc.com/Linux/2012-11/73419.htm

Arch Linux上安裝QEMU+EFI BIOS http://www.linuxidc.com/Linux/2013-02/79560.htm

QEMU的翻譯框架及調試工具 http://www.linuxidc.com/Linux/2012-09/71211.htm

QEMU 的詳細介紹:請點這裡
QEMU 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved