引言
一种类型的虚拟机有其特定的初始化函数以完成虚拟机内存初始化等操作。调用哪个初始化函数在构建虚拟机类时指定:
1 | static const TypeInfo my_machine_info = { |
其中my_machine_class_init
是自定义的机器类的构造方法,用于初始化虚拟机类的各种信息,其中就包括指定虚拟机初始化主函数,如:
1 | static void my_machine_class_init(ObjectClass *oc, void *data) |
使用MACHINE_CLASS(oc)
宏将ObjectClass
类型强制转换成MachineClass mc
对象,然后让mc->init
赋值为自定义的虚拟机初始化主函数my_machine_init
。这样qemu框架在创建虚拟机时就能使用我们指定的方法了。
通过上面的过程我们知道,令mc->init = my_machine_init
后qemu就会执行我们定义的虚拟机初始化函数my_machine_init
。虚拟机初始化包括虚拟机内存的创建,因此我们要在my_machine_init
函数中完成内存初始化部分操作。
我要独立设计内存创建方法则要在my_machine_init
函数中完成内存初始化部分的操作。如初始化ram、rom等
虚拟机初始化函数会接受一个MachineState *machine
作为参数,其中包括虚拟机的基本信息,这些信息由qemu解析命令行参数获得,其中就包含管理虚拟机ram的MemoryRegionmachine->ram
和ram大小的信息machine->ram_size
等。
下面将分别介绍x86、arm和powerpc架构虚拟机创建内存的方法。
x86
x86机器为了实现向后兼容,需要用一个MemoryRegion代表低地址空间的内存,用另一个MemoryRegion代表地址空间的内存。将代表低地址空间的mr称为ram_below_4g
,代表高地址空间的mr称为ram_above_4g
。虽然名称上是以4g为分界点,但实际中是以3g为分界点,即分配给虚拟机的内存大于3g就需要创建ram_above_4g
了。
通过machine->ram_size
可知道为虚拟机分配了多少内存,如果ram_size
大于4g,就需要创建一个mr(ram_above_4g
)来管理高内存地址空间。因此我们的内存初始化需要进行如下操作:
1 | static void my_machine_init(MachineState *machine) |
首先根据machine->ram_size
提供的内存信息进行预处理。如果分配给虚拟机的内存大于规定的阀值lowmem
(3G),那么就要创建代表高地址的mrram_above_4g
,其大小就是低3G以外的部分above_4g_mem_size = machine->ram_size - lowmem
如果给虚拟机的内存低于阀值3G则仅创建代表低地址空间的mr,其大小below_4g_mem_size = machine->ram_size
,见如下代码
1 | ram_addr_t lowmem = 0xc0000000; /* 3G */ |
无论是创建代表高地址mr还是创建代表低地址的mr,都要通过memory_region_init_alias()
将其创建为machine->ram
的别名。表示其管理machine->ram
的一部分。
然后通过memory_region_add_subregion()
接口函数指定高地址、低地址两部分mr在地址空间中的起始位置。
memory_region_add_subregion()
的第一个参数为被加入到的MemoryRegion,第二个参数为待加入的mr在被加入mr中的位置,第三个参数为待加入的mr
代表低地址mr的起始位置为系统内存空间(system_memory
)的0。代表高地址mr的起始位置为system_memory
中的4G。实际代码示例如下:
1 | MemoryRegion *ram_below_4g = g_malloc(sizeof(*ram_below_4g)); |
如果虚拟机的内存大于4g,则还会创意一个管理高4g空间的MemoryRegion结构ram_above_4g
,其同样是machine->ram
的一个别名,其地址空间从4G开始。
1 | if (machine->ram_size > lowmem) { |
之后可以通过x86_bios_rom_init(machine, "bios.bin", rom_memory, true)
函数接口创建bios_rom
,其原型如下:
1 | void x86_bios_rom_init(MachineState *ms, const char *default_firmware, |
第一个参数为machine
,第二个参数为默认设备名,第三个参数为用于管理的MemoryRegion,第四个参数为标记
arm
arm架构虚拟机的内存创建同理,都是要在虚拟机初始化主函数中完成,设自定义的虚拟机初始化函数为my_machine_init
,则my_machine_init
大致应包含如下内容:
1 | static void my_machine_init(MachineState *machine) |
arm架构通过vms
结构管理虚拟机的信息,其中就包括内存地址划分方法。通过virt_set_memmap(vms)
接口函数就能完成vms结构的初始化工作。其中主要会赋值vms->memmap
,这是一个字典结构,每一项都包含内存空间的起始地址和大小。
1 | static const MemMapEntry base_memmap[] = { |
因此首先要调用virt_set_memmap(vms)
初始化vms
,使其获得各部分地址空间信息。
1 | VirtMachineState *vms = VIRT_MACHINE(machine); |
之后通过vms
结构就能方便初始化各部分地址空间。如当要为虚拟机添加内存时可以通过memory_region_add_subregion()
将代表虚拟机内存的MemoryRegionmachine->ram
添加到系统内存地址空间的vms->memmap[VIRT_MEM].base
处
当要创建gic设备时可以通过create_gic(vms)
函数,传入vms结构完成。
同理要创建pcie设备时也要传入vms
结构,通过调用create_pcie(vms)
完成。
其他设备内存空间的创建方法大同小异,都需要vms结构,只是有的可能会需要一些外参数。
如果要自定义地址空间布局则可以通过创建类似的MemMapEntry
结构实现
powerpc
以g3beige
为例,g3beige
类型的虚拟机的初始化函数为ppc_heathrow_init
,其内存初始化部分主要如下:
1 | static void ppc_heathrow_init(MachineState *machine) |
首先g3beige
会限制内存的大小,如果内存大于2047MB则会报错退出
1 | if (ram_size > 2047 * MiB) { |
之后让rammachine->ram
从地址空间0开始:
1 | memory_region_add_subregion(get_system_memory(), 0, machine->ram); |
根据宏PROM_SIZE
(4MB)创建对应大小的rom作为bios
1 | memory_region_init_rom(bios, NULL, "ppc_heathrow.bios", PROM_SIZE, &error_fatal); |
然后根据宏PROM_BASE
(0xffc00000),设置其起始地址
1 | memory_region_add_subregion(get_system_memory(), PROM_BASE, bios); |
mac99
再看mac99类型虚拟机的初始化方法,其初始化主函数为ppc_core99_init
1 | static void ppc_core99_init(MachineState *machine) |
同g3beige
类型的虚拟机,其也是直接将ram分配到地址0,将PROM_SIZE
大小的bios rom分配到地址PROM_BASE
。
总结
不同类型虚拟机的地址空间布局有所不同,内存空间的划分笔者认为主要有两部分,划分ram和划分rom。但不论是ram还是rom其划分方法是一样的:
- 确定管理这块空间的MemoryRegion结构的大小,之后通过
memory_region_init_
系列函数初始化,初始化的mr管理ram则用memory_region_init_ram
,初始化的mr管理rom则用memory_region_init_rom
初始化 - 确定改MemoryRegion结构在地址空间的起始位置,之后通过
memory_region_add_subregion
加到对应的空间
如mac99类型中rom的大小为PROM_SIZE
,就通过下面方式初始化其对应的MemoryRegion
1 | memory_region_init_rom(bios, NULL, "ppc_core99.bios", PROM_SIZE, &error_fatal); |
第一个参数的待初始化的MemoryRegion,第二个参数为该mr的owner,第三个参数为该mr的名称,第四个参数为该mr的大小,最后一个参数用于记录报错信息
rom在地址空间的位置为PROM_BASE
,则通过下面方法添加到对应地址空间
1 | memory_region_add_subregion(get_system_memory(), PROM_BASE, bios); |
第一个参数为被加入到的MemoryRegion,第二个参数为待加入的mr在被加入mr中的位置,第三个参数为待加入的mr