接前一篇文章: QEMU源码全解析 —— 内存虚拟化(2)
本文内容参考:
《 QEMU /KVM》源码解析与应用 —— 李强,机械工业出版社
浅谈QEMU Memory Region 与 Address Space
特此致谢!
QEMU 内存初始化
1. 基本结构
上一回讲解了QEMU中与内存相关的第一个数据结构AddressSpace,并初步介绍了第二个基本数据结MemoryRegion以及这两者之间的关系。本回对于MemoryRegion结构进行深入讲解。
(2)MemoryRegion
MemoryRegion表示的是 虚拟机中的一段内存区域 。 MemoryRegion是内存模拟中的核心结构 , 整个内存的模拟都是通过MemoryRegion构成的无环图完成 的。图的叶子节点是实际分配给 虚拟机 的物理内存或者MMIO,中间节点则表示内存总线,内存控制是其它MemoryRegion的别名。
MemoryRegion是一个树状结构。每个MemoryRegion里会有一个链表头维护child region,还有一个链表单元subregions_link用于挂接到上一级的sub_region链表上,还有向上查找的container指针指向父节点。所以MemoryRegion并不是并列的多个地址块组成(一级链表维护就可以),而是有层级关系的。
为了便于理解,再次贴出struct MemoryRegion的定义,在include/exec/memory.h中,代码如下:
- /** MemoryRegion:
- *
- * A struct representing a memory region.
- */
- struct MemoryRegion {
- Object parent_obj;
-
- /* private: */
-
- /* The following fields should fit in a cache line */
- bool romd_mode;
- bool ram;
- bool subpage;
- bool readonly; /* For RAM regions */
- bool nonvolatile;
- bool rom_device;
- bool flush_coalesced_mmio;
- uint8_t dirty_log_mask;
- bool is_iommu;
- RAMBlock *ram_block;
- Object *owner;
- /* owner as TYPE_DEVICE. Used for re-entrancy checks in MR access hotpath */
- DeviceState *dev;
-
- const MemoryRegionOps *ops;
- void *opaque;
- MemoryRegion *container;
- int mapped_via_alias; /* Mapped via an alias, container might be NULL */
- Int128 size;
- hwaddr addr;
- void (*destructor)(MemoryRegion *mr);
- uint64_t align;
- bool terminates;
- bool ram_device;
- bool enabled;
- bool warning_printed; /* For reservations */
- uint8_t vga_logging_count;
- MemoryRegion *alias;
- hwaddr alias_offset;
- int32_t priority;
- QTAILQ_HEAD(, MemoryRegion) subregions;
- QTAILQ_ENTRY(MemoryRegion) subregions_link;
- QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;
- const char *name;
- unsigned ioeventfd_nb;
- MemoryRegionIoeventfd *ioeventfds;
- RamDiscardManager *rdm; /* Only for RAM */
-
- /* For devices designed to perform re-entrant IO into their own IO MRs */
- bool disable_reentrancy_guard;
- };
对其中比较重要的成员说明如下:
- RAMBlock *ram_block
ram_block表示实际分配的物理内存。后文书会详细分析。
- const MemoryRegionOps *ops
ops 里面是一组回调函数,在对MemoryRegion进行操作时会被调用,如MMIO的读写请求。
- MemoryRegion *container
container表示该MemoryRegion所处的上一级MemoryRegion。
- hwaddr addr
addr表示该MemoryRegion所在的虚拟机的物理地址。
- bool terminates
terminates用来指示是否是叶子节点。
- int32_t priority
priority用于指示MemoryRegion的优先级。
- QTAILQ_HEAD(, MemoryRegion) subregions
subregions将该MemoryRegion所属的子MemoryRegion连接起来。
- QTAILQ_ENTRY(MemoryRegion) subregions_link
subregions_link则用来连接同一个父MemoryRegion下的相同兄弟。
为了便于理解,再次贴出上一回的AddressSpace与MemoryRegion关系图,其实也是MemoryRegion结构拓扑图。
关于MemoryRegion,有以下更多、更进一步说明:
1)MemoryRegion里有基础的hwaddr和size,是针对GUEST视角的地址,即GPA(Guest Physical Addr,客户机物理地址)。还有一个offset;
2)父子MemoryRegion之间是从属关系,每个MemoryRegion涵盖自己所有的child region空间;
3)同一个MemoryRegion下的child列表是并列的,但未必是互斥的,如果有重复的地址空间,则会根据优先级来选择;
4)最上层的MemoryRegion记录在AddressSpace的root变量;
5)有一点需要注意,QEMU里面维护的是整个地址空间,因此MemoryRegion虽然名字是“Memory”,但实际上它并非只针对内存。除了内存,还包括PCI bar空间(memory)、config空间(io/memory)等。guest对所有地址空间的访问,都要由它接管。
常见的MemoryRegion有如下几类:
- RAM
host上 一段实际 分配给虚拟机作为物理内存的虚拟内存 。内存Memory和Cache就是RAM类型。
创建接口为:
memory_region_init_ram() —— 通用的
memory_region_init_resizeable_ram()、 memory_region_init_ram_from_file()、memory_region_init_ram_ptr() —— 特殊的
- MMIO
guest的一段内存 ,但是 在宿主机上没有对应的虚拟内存 ,而是截获对这个区域的访问,调用对应读写函数,用在设备模拟中。
用于host callbacks创建的guest memory,如每次read or write操作会导致host的一个callback。
创建接口为:
memory_region_init_io()
- ROM
与RAM类似,只是该类型内存只有只读属性,无法写入。主要用于闪存,比如存OS的加载器。
创建接口为:
memory_region_init_rom()
- ROM device
其在读方面类似于RAM,而在写方面类似于MMIO,写入会调用对应的写回调函数。像RAM一样read,同时write会调用host callback的device。
创建接口为:
memory_region_init_rom_device()
- IOMMU
有IOMMU职责的region,即能将地址直接翻译到目标的memory region,只能用于IOMMU device。
创建接口为:
memory_region_init_iommu()
- container
包含若干个MemoryRegion,每一个Region在这个container中的偏移都不一样 。container主要用来将多个MemoryRegion合并成一个,如PCI的MemoryRegion就会包括RAM的MMIO。一般来说,container中的region不会重合,但有的时候也有例外。
顾名思义,包含了其它的mr,记录每个mr的offset。container可以管理多个mr为一个单位,十分有用。例如,一个PCI BAR可能包含了一个RAM region和一个MMIO region。
创建接口为:
memory_region_init()
- alias
region的另一部分,可以使一个region被分成几个不连续的部分。
通常情况下,MemoryRegion并不会重叠,当解析一个地址时,只会落入一个MemoryRegion;而有些时候,让MemoryRegion重合也比较有用。但是当MemoryRegion重合时,就需要有一种机制决定到底让哪一个对虚拟机可见,这就是MemoryRegion结构体中priority的作用。
一个region的子集。允许将一个region分解成不连续的子region。
创建接口为:
memory_region_add_subregion() —— 用于将一个已存在的region加入到某个container中
memory_region_init_alias() —— 用于创建一个新的region
以上是从具体的MemoryRegion类型来划分的,从更大的层面可以将MemoryRegion划分为以下三种:
- 根级MemoryRegion
直接 通过memory_region_init函数初始化 , 没有自己的内存 ,用于管理subregion。如 system_memory。
- 实体MemoryRegion
通过memory_region_init_ram函数初始化 , 有自己的内存 (从QEMU进程地址空间中分配),大小为 size。如ram_memory(pc.ram)、pci_memory(pci)等。 这种MemoryRegion中真正的分配物理内存,最主要的就是pc.ram和pci。分配的物理内存的作用分别是内存、PCI地址空间以及fireware空间。QEMU是用户空间代码,分配的物理内存返回的是HVA(Host Virtual Address ),HVA保存至RAMBlock的host域。通过实体MemoryRegion对应的RAMBlock可以管理HVA。
- 别名MemoryRegion
通过memory_region_init_alias函数初始化 , 没有自己的内存 , 表示实体MemoryRegion (如 pc.ram) 的一部分 。通过alias成员指向实体MemoryRegion,alias_offset代表了该别名MemoryRegion所代表的内存起始GPA相对于实体MemoryRegion所代表的内存起始GPA的偏移量,如ram_below_4g、ram_above_4g等。
更多内容请看下回。