深度探索Linux系统虚拟化:原理与实现
上QQ阅读APP看书,第一时间看更新

2.2.2 建立内存段信息

BIOS探测完内存信息后,会将内存信息保存在BIOS的数据区BDA/EBDA中,当bootloader或者OS通过软中断的方式向BIOS询问内存信息时,BIOS中的中断处理函数将从BIOS的数据区中复制内存信息到调用者指定的位置。同样的道理,既然VMM是为操作系统提供一个透明的环境,那么就需要在虚拟BIOS使用的数据区,按照e820记录的格式,准备好内存段的信息。

物理机的内存由多个内存段组成,每个段使用一个e820记录描述,典型的x86架构的32位系统的内存映射如表2-3所示。

表2-3 典型的x86架构的32位系统的内存映射

以kvmtool为例,其组织的内存段如下:


commit 2f3976eeee4e0421c136c3431990a55cbf0f2bbf
kvm: BIOS E820 memory map emulation
kvmtool.git/kvm.c
void kvm__setup_mem(struct kvm *self)
{
    struct e820_entry *mem_map;
    unsigned char *size;

    size        = guest_flat_to_host(self, E820_MAP_SIZE);
    mem_map     = guest_flat_to_host(self, E820_MAP_START);

    *size       = 4;

    mem_map[0]  = (struct e820_entry) {
        .addr       = REAL_MODE_IVT_BEGIN,
        .size       = BDA_END - REAL_MODE_IVT_BEGIN,
        .type       = E820_MEM_RESERVED,
    };
    mem_map[1]  = (struct e820_entry) {
        .addr       = BDA_END,
        .size       = EBDA_END - BDA_END,
        .type       = E820_MEM_USABLE,
    };
    mem_map[2]  = (struct e820_entry) {
        .addr       = EBDA_END,
        .size       = BZ_KERNEL_START - EBDA_END,
        .type       = E820_MEM_RESERVED,
    };
    mem_map[3]  = (struct e820_entry) {
        .addr       = BZ_KERNEL_START,
        .size       = self->ram_size - BZ_KERNEL_START,
        .type       = E820_MEM_USABLE,
    };
}

#define BZ_KERNEL_START         0x100000UL

kvmtool.git/include/kvm/bios.h
#define E820_MAP_SIZE           EBDA_START
#define E820_MAP_START          (EBDA_START + 0x01)

函数kvm__setup_mem建立了4个内存段:第1个内存段用于存储实模式的IVT;第2个内存段是传统的低端内存;第3个是BIOS,包括主板BIOS和显卡BIOS,以及用于显卡相关的内存;第4个是扩展内存区,主要的可用内存都在这个段了。关注第4个内存段的起始位置BZ_KERNEL_START,从名字就可以看出,这是bootloader加载内核时存放内核的内存地址。根据宏BZ_KERNEL_START的值可见,就是在扩内存的起始位置,即物理内存1MB处。

另外,kvmtool将内存段数设置为4,存储在了内存地址E820_MAP_SIZE处,根据宏E820_MAP_SIZE的值可见,就是存储在了EBDA区的第1个字节。内核可以从这个位置读取内存段数,即e820记录的总数。从EBDA区的第2个字节开始,开始存放内存段信息。