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

http://www.ibm.com/developerworks/cn/linux/1410_qiaoly_qemubios/

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

时间: 2024-10-09 23:02:31

QEMU 代码分析:BIOS 的加载过程的相关文章

重温.NET下Assembly的加载过程

原文:重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后发现,并没能解决我的问题,有些点写的不是特别详细,让人看完之后感觉还是云里雾里.最后,我决定重新复习一下这个经典而古老的问题,并将所得总结于此,然后会有一个实例对这个问题进行演示,希望能够帮助到大家. .NET下Assembly的加载过程 .NET下Assembly的加载,最主要的一步就是确定As

Android4.4 Telephony流程分析——联系人(Contact)列表缩略图的加载过程

本文代码以MTK平台Android 4.4.2为分析对象,与Google原生AOSP有些许差异,请读者知悉. Android联系人列表的缩略图加载主要用到ContactPhotoManager.java这个类,这是个抽象类,实现了ComponentCallbacks2接口,其内部有个它的具体实现类,叫ContactPhotoManagerImpl,ContactPhotoManagerImpl继承了ContactPhotoManager并实现了android.os.Handler.Callbac

spring启动component-scan类扫描加载过程---源码分析

有朋友最近问到了 spring 加载类的过程,尤其是基于 annotation 注解的加载过程,有些时候如果由于某些系统部署的问题,加载不到,很是不解!就针对这个问题,我这篇博客说说spring启动过程,用源码来说明,这部分内容也会在书中出现,只是表达方式会稍微有些区别,我将使用spring 3.0的版本来说明(虽然版本有所区别,但是变化并不是特别大),另外,这里会从WEB中使用spring开始,中途会穿插自己通过newClassPathXmlApplicationContext 的区别和联系.

【Spring源码分析系列】启动component-scan类扫描加载过程

原文地址:http://blog.csdn.net/xieyuooo/article/details/9089441/ 在spring 3.0以上大家都一般会配置一个Servelet,如下所示: 1 <servlet> 2 <servlet-name>spring</servlet-name> 3 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-clas

socket实现人多聊天与Java代码加载过程

第一部分是java代码加载过程 关于java代码加载过程,今天调试了阿里巴巴一份代码,如下: /** * 加载方法不等于执行方法,初始化变量则会赋值 * 类加载顺序应为 加载静态方法-初始化静态变量-执行静态代码块 * 实例化时 先加载非静态方法-实例化非静态变量-执行构造代码块-执行构造函数 * @author panteng * */ public class StaticTest { /**第一个加载*/ public static int k = 0; /**第二个加载,因为是new一个

Spring IOC bean加载过程

首先我们不要在学习Spring的开始产生畏难情绪.Spring没有臆想的那么高深,相反,它帮我们再项目开发中制定项目框架,简化项目开发.它的主要功能是将项目开发中繁琐的过程流程化,模式化,使用户仅在固定文件中增加特定标签并实现特定逻辑层的代码就能完成项目开发.下面我们来分析web项目启动时bean的初始化过程. 我们遵循类的依赖,引用关系来理清spring在这一过程中的架构和细节实现.java web项目入口在web.xml,Spring在此配置入口servlet完成bean的加载.Dispat

DICOM:DICOM三大开源库对比分析之“数据加载”

背景: 上一篇博文DICOM:DICOM万能编辑工具之Sante DICOM Editor介绍了DICOM万能编辑工具,在日常使用过程中发现,"只要Sante DICOM Editor打不开的数据,基本可以判定此DICOM文件格式错误(准确率达99.9999%^_^)".在感叹Sante DICOM Editor神器牛掰的同时,想了解一下其底层是如何实现的.通过日常使用以及阅读软件帮助手册推断其底层依赖库很可能是dcmtk,就如同本人使用dcmtk.fo-dicom.dcm4che3等

你所不知道的SQL Server数据库启动过程(用户数据库加载过程的疑难杂症)

前言 本篇主要是上一篇文章的补充篇,上一篇我们介绍了SQL Server服务启动过程所遇到的一些问题和解决方法,可点击查看,我们此篇主要介绍的是SQL Server启动过程中关于用户数据库加载的流程,并且根据加载过程中所遇到的一系列问题提供解决方案. 其实SQL Server作为微软的一款优秀RDBMS,它启动的过程中,本身所带的那些系统库发生问题的情况相对还是很少的,我们在平常使用中,出问题的大部分集中于我们自己建立的用户数据库. 而且,相对于侧重面而言,其实我们更关注的是我们自己建立的用户数

ThinkPHP3.2 加载过程(四)

前言: 由于比较懒散,但是又是有点强迫症,所以还是想继续把ThinkPHP3.2的加载过程这个烂尾楼补充完整. ========================================分割线================================= 上次最后一个篇说道加载APP:run()   ----在ThinkPHP/Library/Think/Thinkclass.php下 在这里说明一下APP在什么时候会被定义并且加载的 配置文件ThinkPHP/Mode/common.