SeaBIOS源码分析-虚拟机启动过程分析

151次阅读
没有评论

共计 14239 个字符,预计需要花费 36 分钟才能阅读完成。

0 SeaBIOS四个阶段

从整体角度出发,SeaBIOS包含四个阶段。

  • 加电自检(Power On Self Test, POST)
  • 引导(Boot)
  • 运行时(Main runtime phase)
  • 继续运行和重启(Resume and reboot)

1 POST阶段

post阶段调用的相关函数如下图所示:

SeaBIOS源码分析-虚拟机启动过程分析

reset_vector

当VCPU开始运行的时候,会从重置向量处开始执行,这“第一条指令”就在romlayout.S的reset_vector中。

reset_vector:
        ljmpw $SEG_BIOS, $entry_post
        // 0xfff5 - BiosDate in misc.c
        // 0xfffe - BiosModelId in misc.c
        // 0xffff - BiosChecksum in misc.c
        .end

前面也分析过了,这是一条长跳转指令,会跳转到CS:IP为$SEG_BIOS:$entry_post的位置,也就是0xF000:0xE05B,

看看0xE05B的位置代码:

        ORG 0xe05b
entry_post:
        cmpl $0, %cs:HaveRunPost                // Check for resume/reboot
        jnz entry_resume
        ENTRY_INTO32 _cfunc32flat_handle_post   // Normal entry point

在romlayout中可以看到他会判断是否经历过POST阶段,如果经历过,意味着当前不应该重新进行,而应该进入继续运行(Resume)。否则就会进入真正的post处理方法:handle_post()。

handle_post

通过ENTRY_INTO32 _cfunc32flat_handle_post语句,即先进入保护模式,然后完成对C函数handle_post的调用。

handle_post函数的定义如下:

void VISIBLE32FLAT
handle_post(void)
{
    if (!CONFIG_QEMU && !CONFIG_COREBOOT)
        return;

    serial_debug_preinit();
    debug_banner();

    // Check if we are running under Xen.
    xen_preinit();

    // Allow writes to modify bios area (0xf0000)
    make_bios_writable();

    // Now that memory is read/writable - start post process.
    //内存可读可写,开始post过程
    dopost();
}

该函数一开始就对串口进行初始化,准备串口输出调试信息,然后检查是否运行在xen平台上,make_bios_writable方法检测运行的平台,最后将ROM BIOS中的bios复制到1M以内的空间,确保0xC0000-0x100000的这段地址空间是可写的,这个复制高地址处的ROM到低地址处的过程被称为Shadow RAM技术。然而,在这个过程后,这段内存会被保护起来,无法进行写入。make_bios_writable函数就用于让这段内存可写,从而便于更改一些静态分配的全局变量值。

dopost

dopost是进一步的初始化。这个函数是POST过程的主体。

// Setup for code relocation and then relocate.
void VISIBLE32INIT
dopost(void)
{
    code_mutable_preinit();

    // Detect ram and setup internal malloc.
    qemu_preinit();
    coreboot_preinit();
    malloc_preinit();

    // Relocate initialization code and call maininit().
    reloc_preinit(maininit, NULL);
}

首先看一下code_mutable_preinit方法。

// Runs after all code is present and prior to any modifications
void
code_mutable_preinit(void)
{
    //全部变量,因为之前经过make_bios_writable方法,可以修改这个变量。
    if (HaveRunPost)
        // Already run
        return;
    // Setup reset-vector entry point (controls legacy reboots).
    //将复位代码(CMOS_RESET_CODE)写入CMOS芯片中的Reset Status寄存器(0xCF)
    rtc_write(CMOS_RESET_CODE, 0);
    barrier();
    HaveRunPost = 1;
    barrier();
}

这一段的核心是将HaveRunPost设置为1。可以看出,HaveRunPost实际上相当于一个全局变量,在BIOS ROM中实际上是被初始化为0的。然后将ROM映射到RAM中的BIOS ROM区域之后,通过make_bios_writable,使得这一段RAM区域可写,然后才能更改HaveRunPost的值。

接着是qemu_preinit();函数,我们尽量不继续深入,探讨具体实现。

void
qemu_preinit(void)
{
    // 读取北桥PCI设备的一些配置信息,开启qemu调试的输出端口,检测并判断当前虚拟机运行的平台PlatformRunningOn,QEMU为1、Xen为2、KVM为4.
    qemu_detect();

    if (!CONFIG_QEMU)
        return;

    if (runningOnXen()) {
        xen_ramsize_preinit();
        return;
    }

    if (!runningOnQEMU()) {
        dprintf(1, "Warning: No QEMU Northbridge found (isapc?)\n");
        PlatformRunningOn |= PF_QEMU;
        kvm_detect();
    }

    // On emulators, get memory size from nvram.
    //读取coms的虚拟机内存大小信息,并加入到e820表
    //e820表通过BIOS向操作系统提供内存布局,通过BIOS的0x15中断进行访问,并将ax设置为0xe820,这也是e820表的由来。
    u32 rs = ((rtc_read(CMOS_MEM_EXTMEM2_LOW) << 16)
              | (rtc_read(CMOS_MEM_EXTMEM2_HIGH) << 24));
    if (rs)
        rs += 16 * 1024 * 1024;
    else
        rs = (((rtc_read(CMOS_MEM_EXTMEM_LOW) << 10)
               | (rtc_read(CMOS_MEM_EXTMEM_HIGH) << 18))
              + 1 * 1024 * 1024);
    RamSize = rs;
    e820_add(0, rs, E820_RAM);

    /* reserve 256KB BIOS area at the end of 4 GB */
    e820_add(0xfffc0000, 256*1024, E820_RESERVED);

    dprintf(1, "RamSize: 0x%08x [cmos]\n", RamSize);
}

接着coreboot_preinit代表是从coreboot启动,这里不再探究。

接着是malloc_preinit();方法,该方法用于SeaBIOS初始化前对系统内存的一些预处理,以便malloc和free操作能够正确地使用系统内存,例如将BIOS区域从映射表删除,以防止被malloc分配使用等,还有就是处理一些高端内存等等。

void
malloc_preinit(void)
{
    ASSERT32FLAT();
    dprintf(3, "malloc preinit\n");

    // Don't declare any memory between 0xa0000 and 0x100000
    //将0xa0000到0x100000之间的内存区域从e820内存映射表中删除,以防止这些区域被分配给malloc库使用。
    e820_remove(BUILD_LOWRAM_END, BUILD_BIOS_ADDR-BUILD_LOWRAM_END);

    // Mark known areas as reserved.
    // 不能再分配给malloc库使用。
    e820_add(BUILD_BIOS_ADDR, BUILD_BIOS_SIZE, E820_RESERVED);

    // Populate temp high ram
    u32 highram = 0;
    int i;
    for (i=e820_count-1; i>=0; i--) {
        struct e820entry *en = &e820_list[i];
        u64 end = en->start + en->size;
        if (end < 1024*1024)
            break;
        if (en->type != E820_RAM || end > 0xffffffff)
            continue;
        u32 s = en->start, e = end;
        if (!highram) {
            u32 newe = ALIGN_DOWN(e - BUILD_MAX_HIGHTABLE, MALLOC_MIN_ALIGN);
            if (newe <= e && newe >= s) {
                highram = newe;
                e = newe;
            }
        }
        alloc_add(&ZoneTmpHigh, s, e);
    }

    // Populate regions
    alloc_add(&ZoneTmpLow, BUILD_STACK_ADDR, BUILD_EBDA_MINIMUM);
    if (highram) {
        alloc_add(&ZoneHigh, highram, highram + BUILD_MAX_HIGHTABLE);
        e820_add(highram, BUILD_MAX_HIGHTABLE, E820_RESERVED);
    }
}

接下来就是reloc_preinit(maininit, NULL)方法。

void __noreturn
reloc_preinit(void *f, void *arg)
{
    void (*func)(void *) __noreturn = f;
    //如果没有启用CONFIG_RELOCATE_INIT选项,则直接调用传入的函数。
    if (!CONFIG_RELOCATE_INIT)
        func(arg);

    // Allocate space for init code.
    //计算位于init section的代码的大小和对齐方式,并为其分配一块临时内存,用于存储重定位后的代码。
    u32 initsize = SYMBOL(code32init_end) - SYMBOL(code32init_start);
    u32 codealign = SYMBOL(_reloc_min_align);
    void *codedest = memalign_tmp(codealign, initsize);
    void *codesrc = VSYMBOL(code32init_start);
    if (!codedest)
        panic("No space for init relocation.\n");

    // Copy code and update relocs (init absolute, init relative, and runtime)
    dprintf(1, "Relocating init from %p to %p (size %d)\n"
            , codesrc, codedest, initsize);
    s32 delta = codedest - codesrc;
    //将init section的代码从代码段(codesrc)复制到临时内存(codedest)中,并更新其中的重定位信息(包括绝对重定位、相对重定位和运行时重定位)。
    memcpy(codedest, codesrc, initsize);
    updateRelocs(codedest, VSYMBOL(_reloc_abs_start), VSYMBOL(_reloc_abs_end)
                 , delta);
    updateRelocs(codedest, VSYMBOL(_reloc_rel_start), VSYMBOL(_reloc_rel_end)
                 , -delta);
    updateRelocs(VSYMBOL(code32flat_start), VSYMBOL(_reloc_init_start)
                 , VSYMBOL(_reloc_init_end), delta);
    //更新函数指针
    if (f >= codesrc && f < VSYMBOL(code32init_end))
        func = f + delta;

    // Call function in relocated code.
    barrier();
    //调用函数
    func(arg);
}

该函数用于在SeaBIOS初始化之前对一些特殊的代码进行重定位和处理,可以看到接下来dopost就是调用了mainint方法进行初始化。

maininit

直接上代码,该函数完成了大量的初始化工作,做了一些注释。

// Main setup code.
static void
maininit(void)
{
    // Initialize internal interfaces.
    //初始化内部接口,包括中断向量表、BIOS数据区等
    interface_init();

    // Setup platform devices.
    //包括设置SMBIOS表,初始化TPM设备,度量SMBIOS使用了1,2号寄存器
    platform_hardware_setup();

    // Start hardware initialization (if threads allowed during optionroms)
    if (threads_during_optionroms())
        device_hardware_setup();

    // Run vga option rom
    //初始化VGA、串口、启用VGA等,这时候会打开屏幕,输出对应的SeaBIOS版本等
    vgarom_setup();
    sercon_setup();
    enable_vga_console();

    // Do hardware initialization (if running synchronously)
    if (!threads_during_optionroms()) {
        device_hardware_setup();
        wait_threads();
    }

    // Run option roms
    //不是VGA的opention rom初始化,发现所有的PCI rom、CBFS ROM(Coreboot Filesystem(CBFS)ROM)以及构建BEV/BCV向量等。
    //包含可选ROM的TPM度量值 扩展进入PCR2
    optionrom_setup();

    // Allow user to modify overall boot order.
    //进入BIOS菜单可以选择启动设备,通过修改DEFAULT_BOOTMENU_WAIT参数可以修改默认等待时间
    //这里面还可以改变TPM的菜单,包含clear TPM和改变active PCR banks
    interactive_bootmenu();
    wait_threads();

    // Prepare for boot.
    //准备进入boot阶段
    prepareboot();

    // Write protect bios memory.
    //自检工作完成,设置bios只读,准备开始boot阶段
    make_bios_readonly();

    // Invoke int 19 to start boot process.
    startBoot();
}

interface_init

我们关注一下interface_init();干了些什么?

void
interface_init(void)
{
    // Running at new code address - do code relocation fixups
    //进行内存分配器的初始化工作,以便后续的接口和模块能够正确地使用系统内存。
    malloc_init();

    // Setup romfile items.
    //读取qemu通过fw_cfg设备传过来的配置信息
    qemu_cfg_init();
    //初始化Coreboot文件系统(CBFS)的相关接口。
    coreboot_cbfs_init();
    //初始化多重引导(multiboot)的相关接口。
    multiboot_init();

    // Setup ivt/bda/ebda
    //中断向量表相关接口初始化
    ivt_init();
    //BIOS—数据区域相关接口初始化
    bda_init();

    // Other interfaces
    //初始化启动设备的相关接口。
    boot_init();
    //初始化BIOS 32位调用的相关接口。
    bios32_init();
    //初始化物理内存管理(PMM)的相关接口。
    pmm_init();
    //初始化即插即用(Plug and Play)的相关接口。
    pnp_init();
    //键盘数据区
    kbd_init();
    //鼠标数据区
    mouse_init();
}

我们在看一下中断向量表初始化内容ivt_init:

中断向量表(Interrupt Vector Table)是一张在实模式下使用的表,这个表将中断号映射到中断处理程序的一个列表。中断向量表必须存储在低地址区域(也就是从0x00000000)开始,大小一般是0x400字节。是一块由很多个项组成的连续的内存空间。

static void
ivt_init(void)
{
    dprintf(3, "init ivt\n");

    // Initialize all vectors to the default handler.
    int i;
    //首先将所有的中断向量都初始化为默认的中断处理程序(entry_iret_official)。
    for (i=0; i<256; i++)
        SET_IVT(i, FUNC16(entry_iret_official));

    // Initialize all hw vectors to a default hw handler.
    // 初始化硬件中断
    for (i=BIOS_HWIRQ0_VECTOR; i<BIOS_HWIRQ0_VECTOR+8; i++)
        SET_IVT(i, FUNC16(entry_hwpic1));
    for (i=BIOS_HWIRQ8_VECTOR; i<BIOS_HWIRQ8_VECTOR+8; i++)
        SET_IVT(i, FUNC16(entry_hwpic2));

    // Initialize software handlers.
    //初始化常见的软件中断,包括
    SET_IVT(0x02, FUNC16(entry_02));
    SET_IVT(0x05, FUNC16(entry_05));
    SET_IVT(0x10, FUNC16(entry_10));
    SET_IVT(0x11, FUNC16(entry_11));
    SET_IVT(0x12, FUNC16(entry_12));
    SET_IVT(0x13, FUNC16(entry_13_official));
    SET_IVT(0x14, FUNC16(entry_14));
    SET_IVT(0x15, FUNC16(entry_15_official));
    SET_IVT(0x16, FUNC16(entry_16));
    SET_IVT(0x17, FUNC16(entry_17));
    SET_IVT(0x18, FUNC16(entry_18));
    SET_IVT(0x19, FUNC16(entry_19_official));
    SET_IVT(0x1a, FUNC16(entry_1a_official));
    SET_IVT(0x40, FUNC16(entry_40));

    // INT 60h-66h reserved for user interrupt
    //用户保留中断
    for (i=0x60; i<=0x66; i++)
        SET_IVT(i, SEGOFF(0, 0));

    // set vector 0x79 to zero
    // this is used by 'gardian angel' protection system
    //用于保护系统
    SET_IVT(0x79, SEGOFF(0, 0));
}

可以看到,这些定义了硬件中断、软件中断、用户保留中断以及保护中断,大多数中断处理程序有在对应的romlayout.S中的入口。

platform_hardware_setup

直接看代码:

static void
platform_hardware_setup(void)
{
    // Make sure legacy DMA isn't running.
    //确保不会运行传统的DMA(直接内存访问),因为它可能会与其他硬件组件冲突
    dma_setup();

    // Init base pc hardware.
    pic_setup();
    thread_setup();
    mathcp_setup();

    // Platform specific setup
    qemu_platform_setup();
    coreboot_platform_setup();

    // Setup timers and periodic clock interrupt
    //设置定时器和周期性时钟中断,
    timer_setup();
    clock_setup();

    // Initialize TPM
    //初始化可信计算模块
    tpm_setup();
}

其中有两个函数我们关注一下,qemu_platform_setup和tpm_setup。

对于qemu_platform_setup,在这个函数中会进行PCI设备的探测,然后SeaBIOS会得到所有设备需要的MMIO和I/O端口资源,然后统一在虚拟机的物理地址空间进行分配。接着完成smm、mtrr寄存器、smp等初始化,然后建立各种表,如PCI interrupt routing表。值得注意的是,早期QEMU ACPI表是SeaBIOS构建的,但是后来QEMU自己创建acpi表就不再依赖SeaBIOS的构建的,也就是if (CONFIG_FW_ROMFILE_LOAD),如果为真,则直接调用find_acpi_rsdp,如果获取到了就直接返回了。

void
qemu_platform_setup(void)
{
    if (!CONFIG_QEMU)
        return;

    if (runningOnXen()) {
        pci_probe_devices();
        xen_hypercall_setup();
        xen_biostable_setup();
        return;
    }

    // Initialize pci
    pci_setup();
    smm_device_setup();
    smm_setup();

    // Initialize mtrr, msr_feature_control and smp
    mtrr_setup();
    msr_feature_control_setup();
    smp_setup();

    // Create bios tables
    if (MaxCountCPUs <= 255) {
        pirtable_setup();
        mptable_setup();
    }
    smbios_setup();

    if (CONFIG_FW_ROMFILE_LOAD) {
        int loader_err;

        dprintf(3, "load ACPI tables\n");

        loader_err = romfile_loader_execute("etc/table-loader");

        RsdpAddr = find_acpi_rsdp();

        if (RsdpAddr)
            return;

        /* If present, loader should have installed an RSDP.
         * Not installed? We might still be able to continue
         * using the builtin RSDP.
         */
        if (!loader_err)
            warn_internalerror();
    }

    acpi_setup();
}

对于tpm_setup,用于初始化可信计算模块。

void
tpm_setup(void)
{
    if (!CONFIG_TCGBIOS)
        return;

    //探测tpm2设备
    int ret = tpm_tpm2_probe();
    if (ret) {
        //探测tpm1.2设备
        ret = tpm_tcpa_probe();
        if (ret)
            return;
    }

    //获取TPM版本 u8 TPM_version
    TPM_version = tpmhw_probe();
    if (TPM_version == TPM_VERSION_NONE)
        return;

    dprintf(DEBUG_tcg,
            "TCGBIOS: Detected a TPM %s.\n",
             (TPM_version == TPM_VERSION_1_2) ? "1.2" : "2");

    TPM_working = 1;

    if (runningOnXen())
        return;

    //初始化TPM
    ret = tpm_startup();
    if (ret)
        return;

    //度量smbios
    tpm_smbios_measure();
    //添加度量日志
    tpm_add_action(2, "Start Option ROM Scan");
}

optionrom_setup

optionrom_setup(void) -> init_pcirom(pci, 0, sources); -> init_optionrom->tpm_option_rom

会将所有option rom进行度量并扩展进入PCR2中。

interactive_bootmenu

BIOS引导菜单,可以按ESC键进入,但是由于默认等待时间只有2.5s,可能来不及点击,可以修改默认等待时间DEFAULT_BOOTMENU_WAIT参数。

进入菜单后,会让你选择引导设备,TPM设置(如果有TPM设备的话)等。

prepareboot();

引导前准备阶段:

void
prepareboot(void)
{
    // Change TPM phys. presence state befor leaving BIOS
    //获取TPM授权密钥,并且接着调用了方法,
    //tpm_add_action(4, "Calling INT 19h");添加了Calling INT 19h度量事件并扩展到了PCR4中
    //tpm_add_event_separators(); 从PCR0-7分别添加事件分隔符
    tpm_prepboot();

    // Run BCVs
    bcv_prepboot();

    // Finalize data structures before boot
    //一系列的最终数据结构准备,为boot阶段做准备,内存收回等等
    cdrom_prepboot();
    pmm_prepboot();
    malloc_prepboot();
    e820_prepboot();

    HaveRunPost = 2;

    // Setup bios checksum.
    BiosChecksum -= checksum((u8*)BUILD_BIOS_ADDR, BUILD_BIOS_SIZE);
}

make_bios_readonly

自检结束,要开始启动了,设置0xF0000-0x100000内存段只读。

// Make the BIOS code segment area (0xf0000) read-only.
void
make_bios_readonly(void)
{
    if (!CONFIG_QEMU || runningOnXen())
        return;
    dprintf(3, "locking shadow ram\n");

    if (ShadowBDF < 0) {
        dprintf(1, "Unable to lock ram - bridge not found\n");
        return;
    }

    u16 device = pci_config_readw(ShadowBDF, PCI_DEVICE_ID);
    if (device == PCI_DEVICE_ID_INTEL_82441)
        make_bios_readonly_intel(ShadowBDF, I440FX_PAM0);
    else
        make_bios_readonly_intel(ShadowBDF, Q35_HOST_BRIDGE_PAM0);
}

startBoot

startBoot函数最终是调用INT 19H中断来,进入BOOT阶段。

// Begin the boot process by invoking an int0x19 in 16bit mode.
void VISIBLE32FLAT
startBoot(void)
{
    // Clear low-memory allocations (required by PMM spec).
    memset((void*)BUILD_STACK_ADDR, 0, BUILD_EBDA_MINIMUM - BUILD_STACK_ADDR);

    dprintf(3, "Jump to int19\n");
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    call16_int(0x19, &br); //回到实模式,进入Boot状态
}

2 boot阶段

0x19中断的入口,也是在layoutrom.S文件中,为entry_19,直接跳转到了handle_19,后者主要作用就是do_boot开始引导。

handle_19

// INT 19h Boot Load Service Entry Point
void VISIBLE32FLAT
handle_19(void)
{
    debug_enter(NULL, DEBUG_HDL_19);
    BootSequence = 0;
    do_boot(0);
}

do_boot

主要的引导函数

// Determine next boot method and attempt a boot using it.
static void
do_boot(int seq_nr)
{
    if (! CONFIG_BOOT)
        panic("Boot support not compiled in.\n");

    if (seq_nr >= BEVCount)
        boot_fail();

    // Boot the given BEV type.
    struct bev_s *ie = &BEV[seq_nr];
    switch (ie->type) {
    case IPL_TYPE_FLOPPY:
        printf("Booting from Floppy...\n");
        boot_disk(0x00, CheckFloppySig);
        break;
    case IPL_TYPE_HARDDISK:
        printf("Booting from Hard Disk...\n");
        boot_disk(0x80, 1);
        break;
    case IPL_TYPE_CDROM:
        boot_cdrom((void*)ie->vector);
        break;
    case IPL_TYPE_CBFS:
        boot_cbfs((void*)ie->vector);
        break;
    case IPL_TYPE_BEV:
        boot_rom(ie->vector);
        break;
    case IPL_TYPE_HALT:
        boot_fail();
        break;
    }

    // Boot failed: invoke the boot recovery function
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    call16_int(0x18, &br);
}

可以看到,在boot之前SeaBIOS已经准备好了各个启动设备并放入了BEV数组中,可以看到,可引导设备,为软盘、硬盘、CDROM等一系列设备中引导都提供了支持,如果尝试启动失败,那么就会调用0x18中断,0x18中断实质上是添加BootSequence,然后继续执行do_boot,当没有一个设备可以启动就会调用boot_fail(),可看到最后一项IPL_TYPE_HALT,最后就会打印"No bootable device"。

我们主要看一下虚拟机从硬盘disk启动的场景,因为这是使用最多的。

boot_disk 从硬盘启动

boot_disk(0x80, 1);

//MBR结构
struct mbr_s {
    u8 code[440];
    // 0x01b8
    u32 diskseg;
    // 0x01bc
    u16 null;
    // 0x01be
    struct partition_s partitions[4];
    // 0x01fe
    u16 signature;
} PACKED; 

// Boot from a disk (either floppy or harddrive)
static void
boot_disk(u8 bootdrv, int checksig)
{
    u16 bootseg = 0x07c0;

    // Read sector
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.dl = bootdrv;
    br.es = bootseg;
    br.ah = 2;
    br.al = 1;
    br.cl = 1;
    //调用0x13中断读取磁盘数据到内存
    call16_int(0x13, &br);

    if (br.flags & F_CF) {
        printf("Boot failed: could not read the boot disk\n\n");
        return;
    }

    if (checksig) {
        struct mbr_s *mbr = (void*)0;
        //判断是不是以0xaa55结尾,是否是可引导设备MBR
        if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
            printf("Boot failed: not a bootable disk\n\n");
            return;
        }
    }

    //TPM度量可引导设备
    tpm_add_bcv(bootdrv, MAKE_FLATPTR(bootseg, 0), 512);

    /* Canonicalize bootseg:bootip */
    u16 bootip = (bootseg & 0x0fff) << 4;
    bootseg &= 0xf000;

    //跳转到 0x7c00执行MBR,并准备寄存器
    call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
}

首先,调用BIOS 0x13中断读取磁盘数据到内存,这里读取的是第一个扇区,放到内存0x7c00的位置,也就是MBR开始运行的地方,然后调用TPM进行度量MBR,最后调用call_boot_entry跳转到0x7c00,这样MBR就开始运行了,控制权就转移了,之后MBR就会加载操作系统内核。。

3 main和runtime阶段

BIOS移交控制权以后,可能以后的程序,包括操作系统,他们有可能会调用SeaBIOS的一些信息,如访问ACPI表或者调用BIOS的中断,就是中断向量表中的内容,定义在layoutrom.S中,形如entry_xxx函数,xxx代表中断号。

4 Resume and reboot阶段

当虚拟机发生错误或者操作系统发出重启请求时,就会进入这个阶段。SeaBIOS处理重启的入口也在entry_post,但是这次会调用entry_resume,从而进入到handle_resume函数里面。

备注

后续还会介绍重点关注,与vtpm设备的交互情况。

正文完
 
landery
版权声明:本站原创文章,由 landery 2023-06-09发表,共计14239字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)