虚拟化容器,大数据,DBA,中间件,监控。

QEMU 代码分析:BIOS 的加载过程

17 03月
作者:admin|分类:容器虚拟化

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 硬件初始化函数调用流程图:

在 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 创建的虚拟机中的内存的过程。


浏览2319 评论0
返回
目录
返回
首页
正则表达式 iSCSI 多路径实现 KVM 高可用