文章目录

  • 扁平化视图
  • FlatView
  • 数据结构
  • FlatView初始化
  • FlatRange
  • 数据结构
  • 组织结构
  • 初始状态
  • 新增IO区间
  • 内存拓扑变更分析
  • 新建内存和IO地址空间
  • 细分内存地址空间


  • 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地址空间的组织结构如下:
  1. 虚拟机的IO地址区间为0 ~ 65535,有一个全局变量address_space_io指向IO地址区间,表示一个逻辑地址空间,这个结构体没有记录虚机物理地址范围
  2. AdressSpace没有记录虚机物理地址范围,它的两个字段root和current_map从不同角度描述了这段内存,并且都记录了虚机的IO地址区间范围,两个字段的类型分别时MemoryRegion和FlatView
  3. IO地址区间在初始化后,其组织结构如下:
新增IO区间
  1. IO MemoryRegion是个容器,当qemu在初始化增加新的硬件设备时,该设备可能会占用IO地址空间,在虚拟机里面通过/proc/ioports可以查询。添加新的IO地址区间会分割系统的IO MR,将其逐渐碎片化。这里我们以增加kvmvapic为例,这个MR属于IO地址区间的子MR,占两个字节,地址区间为126 ~ 128,如下:
  2. 当IO MemoryRegion容器成功添加kvmvapic MR之后,会对应的生成一段FlatRange,它是MR对应的扁平化视图,子MR的添加将原来的FlatRange分成了三个区间,区间1是0 ~ 126,区间2是126 ~ 128,区间3是128 ~ 65535,如下图所示:
  3. 通过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");
}
  1. 内存地址空间是qemu创建的第一个地址空间,在创建该地址空间之前,address_spaces仅仅完成了初始化,而flat_views还是空的
  2. 执行 address_space_init(&address_space_memory, system_memory, "memory")函数之后,内存地址空间初始化完成,有了第一个地址空间,此时address_spaces和flat_views信息如下。可以看到,内存地址空间初始化完成后,只是将flat_views初始化并设置了默认的空FlatView,这个hash表里面没有实际的内存地址区间信息,因此,可以说,内存地址空间初始化之后,对应的Root MR没有生成对应的FlatView并添加到全局的hash表中。
  3. 执行 address_space_init(&address_space_io, system_io, "I/O")函数之后,IO地址空间初始化完成,IO地址空间的增加改变了内存的拓扑,flat_views hash表中记录了IO地址空间生成的FlatView,此时IO地址空间由一段FlatRange组成,随着初始化的进行,IO地址空间会进一步被切割成多段。

细分内存地址空间

  1. 从上面看出,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);
  1. kvm-apic-msi的MR申请之后,就向系统内存空间中添加此MR,这一步在memory_region_add_subregion_overlap中完成。这个过程涉及到内存地址空间拓扑的更新,在此之前地址空间有三个,分别是memory,I/O和cpu-memory-0:
  2. flat_views存储的(MR,FlatView)键值对有两个,一个默认的empty_view,一个是IO地址空间的MR和FlatView
  3. 在往系统的内存地址空间中添加子MR apic->io_memory之后,flat_views包含的键值对增加了1个,如下图所示,这个键值对的key是系统内存的根MR,但对应的FlatView只包含了一个range并且没有将根MR描述的2^64大小的区间表示完,它只是其中的一个子集(0xfee00000,0xfee00000 + 100000)