- qemu为了模拟虚机内存,必须对虚拟机的内存地址空间进行管理,当内存拓扑发生变化时qemu模拟的内存映像需要随之调整。本文主要介绍为了管理虚机内存的地址空间,qemu设计的FlatView和FlagRange数据结构
扁平化视图
FlatView
数据结构
- FlatView是AdressSpace扁平化展开后的视图。表示虚拟机的物理地址空间。
/* Flattened global view of current active memory hierarchy. Kept in sorted
* order.
*/
struct FlatView {
struct rcu_head rcu;
unsigned ref;
FlatRange *ranges; /* 1 */
unsigned nr; /* 2 */
unsigned nr_allocated; /* 3 */
struct AddressSpaceDispatch *dispatch;
MemoryRegion *root; /* 指向关联的Root MR */
};
1. 每个FlatView包含一组内存地址区间,构成整个内存地址空间
2. 当前已经使用的ranges数组元素个数
3. 为ranges分配的总数组大小,当nr超过nr_allocated时,会重新分配地址空间并复制给ranges,该操作在flatview_insert中实现
FlatView初始化
- 每个FlatView都关联到一个地址空间,FlatView的root结构体在地址空间初始化时作为输入
FlatRange
数据结构
- FlatRange表示虚机的一段物理地址区间,一个MR由多个FlatRang,FlatRange与MR是多对一的关系
/* Range of memory in the global map. Addresses are absolute. */
struct FlatRange {
MemoryRegion *mr; /* 指向所属的MR */
hwaddr offset_in_region; /* 相对MR的offset */
AddrRange addr; /* 虚机的物理地址区间,由起始地址(start)和长度(size)组成 */
......
};
struct AddrRange {
Int128 start; /* 内存区间起始物理地址,也就是该内存区间在地址空间的偏移 */
Int128 size; /* 内存区间长度 */
};
- 打印内存地址
address_space_memory
空间下面的第1、2个FlatRange,如下图所示,可以看到他们都只指向同一个MR(pc.ram)。根据起始地址和大小,可以确定两个rang的区间地址分别是[0, 0xbffff]
和[0xc0000, 0xc1fff]
。ranges[0]在MR中的偏移(offset_in_region)为0,ranges[1]在MR中的偏移为786432(0xc0000)。
- 我们也可以通过
virsh qemu-monitor-command vm --hmp info mtree -f
打印虚机FlatView的信息,如下:
组织结构
初始状态
- 以虚拟机IO地址空间为例,在IO地址空间初始化之后,打印出IO地址空间的组织结构如下:
- 虚拟机的IO地址区间为0 ~ 65535,有一个全局变量address_space_io指向IO地址区间,表示一个逻辑地址空间,这个结构体没有记录虚机物理地址范围
- AdressSpace没有记录虚机物理地址范围,它的两个字段root和current_map从不同角度描述了这段内存,并且都记录了虚机的IO地址区间范围,两个字段的类型分别时MemoryRegion和FlatView
- IO地址区间在初始化后,其组织结构如下:
新增IO区间
- IO MemoryRegion是个容器,当qemu在初始化增加新的硬件设备时,该设备可能会占用IO地址空间,在虚拟机里面通过/proc/ioports可以查询。添加新的IO地址区间会分割系统的IO MR,将其逐渐碎片化。这里我们以增加kvmvapic为例,这个MR属于IO地址区间的子MR,占两个字节,地址区间为126 ~ 128,如下:
- 当IO MemoryRegion容器成功添加kvmvapic MR之后,会对应的生成一段FlatRange,它是MR对应的扁平化视图,子MR的添加将原来的FlatRange分成了三个区间,区间1是0 ~ 126,区间2是126 ~ 128,区间3是128 ~ 65535,如下图所示:
- 通过
virsh qemu-monitor-command vm --hmp info mtree
命令查看IO地址空间,可以验证上面的组织结构信息
内存拓扑变更分析
- qemu在虚机启动过程中,首先为虚机提供一个逻辑物理地址空间,随着硬件的增加,这个物理区间被不断分割,变成一段一段更小的虚机物理地址区间,直到最终完成整个虚机硬件的模拟。这个过程中,
address_spaces
保存了物理地址空间链表头,随qemu初始化而不断更新,flat_views
保存地址空间关联的(Root MR,FlatView)
键值对,也会随qemu初始化不断更新。通过分析这两个数据结构,可以更感性地认识qemu的内存组织结构。
新建内存和IO地址空间
static void memory_map_init(void)
{
system_memory = g_malloc(sizeof(*system_memory));
memory_region_init(system_memory, NULL, "system", UINT64_MAX);
address_space_init(&address_space_memory, system_memory, "memory");
system_io = g_malloc(sizeof(*system_io));
memory_region_init_io(system_io, NULL, &unassigned_io_ops, NULL, "io", 65536);
address_space_init(&address_space_io, system_io, "I/O");
}
- 内存地址空间是qemu创建的第一个地址空间,在创建该地址空间之前,address_spaces仅仅完成了初始化,而flat_views还是空的
- 执行
address_space_init(&address_space_memory, system_memory, "memory")
函数之后,内存地址空间初始化完成,有了第一个地址空间,此时address_spaces和flat_views信息如下。可以看到,内存地址空间初始化完成后,只是将flat_views初始化并设置了默认的空FlatView,这个hash表里面没有实际的内存地址区间信息,因此,可以说,内存地址空间初始化之后,对应的Root MR没有生成对应的FlatView并添加到全局的hash表中。
- 执行
address_space_init(&address_space_io, system_io, "I/O")
函数之后,IO地址空间初始化完成,IO地址空间的增加改变了内存的拓扑,flat_views hash表中记录了IO地址空间生成的FlatView,此时IO地址空间由一段FlatRange组成,随着初始化的进行,IO地址空间会进一步被切割成多段。
细分内存地址空间
- 从上面看出,qemu在初始化内存地址空间和IO地址空间时,内存地址空间并没有生成FlatView。在cpu初始化过程中,对msi类型的中断实现,qemu需要实现内存模拟,这段内存是MMIO类型的物理内存。中断控制器的MR的声明和初始化流程如下,MR被保存到了APICCommonState结构体的io_memory字段中
x86_cpu_apic_realize
/* 声明并初始化kvm-apic-msi的MR */
object_property_set_bool(OBJECT(cpu->apic_state), true, "realized", errp);
k->realize = kvm_apic_realize
memory_region_init_io(&s->io_memory, OBJECT(s), &kvm_apic_io_ops, s, "kvm-apic-msi", APIC_SPACE_SIZE);
apic = APIC_COMMON(cpu->apic_state);
/* 将kvm-apic-msi的MR添加到系统内存中,地址区间为(apicbase,apicbase + mr->size)*/
memory_region_add_subregion_overlap(get_system_memory(),
apic->apicbase &
MSR_IA32_APICBASE_BASE,
&apic->io_memory,
0x1000);
- kvm-apic-msi的MR申请之后,就向系统内存空间中添加此MR,这一步在memory_region_add_subregion_overlap中完成。这个过程涉及到内存地址空间拓扑的更新,在此之前地址空间有三个,分别是memory,I/O和cpu-memory-0:
- flat_views存储的(MR,FlatView)键值对有两个,一个默认的empty_view,一个是IO地址空间的MR和FlatView
- 在往系统的内存地址空间中添加子MR apic->io_memory之后,flat_views包含的键值对增加了1个,如下图所示,这个键值对的key是系统内存的根MR,但对应的FlatView只包含了一个range并且没有将根MR描述的2^64大小的区间表示完,它只是其中的一个子集(0xfee00000,0xfee00000 + 100000)