在分析了KVM中对虚拟机各级地址(gva->gpa->hva->hpa)的转换之后,想要知道qemu中又是如何完成各级地址转换的,因此对qemu中对虚拟机内存管理的相关数据结构与源码进行了分析。qemu中对于虚拟机内存管理涉及的数据结构较多,仅gpa->hpa的转换过程涉及的数据结构就有:MemoryRegion, AddressSpace, MemoryRegionSection, Flatview, FlatRange, RAMBlock, RAMList等。

这几个数据结构的关系刚接触时有些混乱,以下试图从gpa到hva的转换来整理这几个数据结构之间的关系。

qemu源码版本为qemu-2.8.0

一、MemoryRegion

QEMU通过MemoryRegion来管理虚拟机内存,通过内存属性,GUEST物理地址等特点对内存分类,就形成了多个MemoryRegion,这些MemoryRegion 通过树状组织起来,挂接到根MemoryRegion下。每个MemoryRegion树代表了一类作用的内存,如系统内存空间(system_memory)或IO内存空间(system_io),这两个是qemu中的两个全局MemoryRegion。

struct MemoryRegion {
Object parent_obj; /* All fields are private - violators will be prosecuted */ /* The following fields should fit in a cache line */
bool romd_mode;
bool ram;
bool subpage;
bool readonly; /* For RAM regions */
bool rom_device;
bool flush_coalesced_mmio;
bool global_locking;
uint8_t dirty_log_mask;
RAMBlock *ram_block; //指向对应的RAMBlock
Object *owner;
const MemoryRegionIOMMUOps *iommu_ops; const MemoryRegionOps *ops;
void *opaque;
MemoryRegion *container; //指向父MR
Int128 size; //区域大小
hwaddr addr; //在父MR中的偏移量
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; //指向实体MR
hwaddr alias_offset;// 起始地址 (GPA) 在实体 MemoryRegion 中的偏移量
int32_t priority;
QTAILQ_HEAD(subregions, MemoryRegion) subregions; //子区域链表头
QTAILQ_ENTRY(MemoryRegion) subregions_link; //子区域链表结点
QTAILQ_HEAD(coalesced_ranges, CoalescedMemoryRange) coalesced;
const char *name;
unsigned ioeventfd_nb;
MemoryRegionIoeventfd *ioeventfds;
QLIST_HEAD(, IOMMUNotifier) iommu_notify;
IOMMUNotifierFlag iommu_notify_flags;
};

MemoryRegion 表示在 Guest memory layout 中的一段内存,可将 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,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 等。

代码中常见的 MemoryRegion 关系为:

                   alias
ram_memory (pc.ram) - ram_below_4g(ram-below-4g)
- ram_above_4g(ram-above-4g) sub
system_memory(system) - ram_below_4g(ram-below-4g)
- ram_above_4g(ram-above-4g)
- pcms->hotplug_memory.mr 热插拔内存

实际上虚拟机的ram申请时是一次性申请的一个完成的ram,记录在一个MR中,之后又对此ram按照size进行了划分,形成subregion,而subregion 的alias便指向原始的MR,而alias_offset 便是在原始ram中的偏移。对于系统地址空间的ram,会把刚才得到的subregion注册到系统中,父MR是刚才提到的全局MR system_memory,subregions_link是链表节点。addr是子MR相对于父MR的偏移,在函数pc_memory_init()函数中有对实体MemoryRegion和别名MemoryRegion的初始化:

void pc_memory_init(PCMachineState *pcms,
MemoryRegion *system_memory,
MemoryRegion *rom_memory,
MemoryRegion **ram_memory)
{
int linux_boot, i;
MemoryRegion *ram, *option_rom_mr;
MemoryRegion *ram_below_4g, *ram_above_4g;
FWCfgState *fw_cfg;
MachineState *machine = MACHINE(pcms);
PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); assert(machine->ram_size == pcms->below_4g_mem_size +
pcms->above_4g_mem_size); linux_boot = (machine->kernel_filename != NULL); /* Allocate RAM. We allocate it as a single memory region and use
* aliases to address portions of it, mostly for backwards compatibility
* with older qemus that used qemu_ram_alloc().
*/
ram = g_malloc(sizeof(*ram));
memory_region_allocate_system_memory(ram, NULL, "pc.ram",
machine->ram_size); //初始化实体MR pc.ram
*ram_memory = ram;
ram_below_4g = g_malloc(sizeof(*ram_below_4g));
memory_region_init_alias(ram_below_4g, NULL, "ram-below-4g", ram,
, pcms->below_4g_mem_size); //初始化别名MR ram_below_4g,将其alias指向ram,alias_offset为0
memory_region_add_subregion(system_memory, , ram_below_4g); //将别名MRram_below_4g添加为system_memory的subregion,设置偏移addr为0
e820_add_entry(, pcms->below_4g_mem_size, E820_RAM);
if (pcms->above_4g_mem_size > ) {
ram_above_4g = g_malloc(sizeof(*ram_above_4g));
memory_region_init_alias(ram_above_4g, NULL, "ram-above-4g", ram,
pcms->below_4g_mem_size,
pcms->above_4g_mem_size); //初始化别名MR ram_above_4g,将其alias指向ram,alias_offset为below_4g_mem_size
memory_region_add_subregion(system_memory, 0x100000000ULL,
ram_above_4g);//同上述ram_below_4g,初始化并添加ram_above_4g,设置偏移addr为0x100000000ull,即4g
e820_add_entry(0x100000000ULL, pcms->above_4g_mem_size, E820_RAM);
}
void memory_region_init_alias(MemoryRegion *mr,
Object *owner,
const char *name,
MemoryRegion *orig,
hwaddr offset,
uint64_t size)
{
memory_region_init(mr, owner, name, size);
mr->alias = orig; //别名MR的alias指向原实体MR
mr->alias_offset = offset; //alias_offset表示偏移
}
static void memory_region_add_subregion_common(MemoryRegion *mr,
hwaddr offset,
MemoryRegion *subregion)
{
assert(!subregion->container);
subregion->container = mr;
subregion->addr = offset; //将addr设置为offset
memory_region_update_container_subregions(subregion);
} void memory_region_add_subregion(MemoryRegion *mr,
hwaddr offset,
MemoryRegion *subregion)
{
subregion->priority = ;
memory_region_add_subregion_common(mr, offset, subregion);
}

可见subregion的addr即为相对于父MR的偏移,对于ram_below_4g,addr为0,对于ram_above_4g,偏移则为4g,而alias_offset为相对于实体MR的偏移量,对于ram_below_4g,alias_offset为0,对于ram_above_4g,alias_offset为ram_below_4g_size,即为4g。

二、RAMBlock

上面提到了qemu为虚拟机分配的内存的hva保存在RAMblock的host域,RAMBlock的定义如下:

struct RAMBlock {
struct rcu_head rcu; // 用于保护 Read-Copy-Update
struct MemoryRegion *mr; // 对应的 MemoryRegion
uint8_t *host; // 对应的 HVA
ram_addr_t offset; // 在 ram_list 地址空间中的偏移 (要把前面 block 的 size 都加起来)
ram_addr_t used_length; // 当前使用的长度
ram_addr_t max_length; // 总长度
void (*resized)(const char*, uint64_t length, void *host); // resize 函数
uint32_t flags;
/* Protected by iothread lock. */
char idstr[]; // id
/* RCU-enabled, writes protected by the ramlist lock */
QLIST_ENTRY(RAMBlock) next; // 指向在 ram_list.blocks 中的下一个 block
int fd; // 映射文件的文件描述符
size_t page_size; // page 大小,一般和 host 保持一致
};

一个RAMBlock表示一段虚拟内存,host域指向申请的ram的虚拟地址,即hva。所有的RAMBlock通过next字段连接起来,表头保存在全局RAMList中,offset表示当前RAMBlock在RAMList中的偏移。每个RAMBlock都有一个唯一的MemoryRegion对应,但需要注意的是不是每个MemoryRegion都有RAMBlock对应。

在函数pc_memory_init()中为实体memoryregion分配内存时,调用了函数memory_region_allocate_system_memory(),非numa架构下调用函数allocate_system_memory_nonnuma(),继而调用memory_region_init_ram_from_file():

#ifdef __linux__
void memory_region_init_ram_from_file(MemoryRegion *mr,
struct Object *owner,
const char *name,
uint64_t size,
bool share,
const char *path,
Error **errp)
{
memory_region_init(mr, owner, name, size);
mr->ram = true;
mr->terminates = true;
mr->destructor = memory_region_destructor_ram;
mr->ram_block = qemu_ram_alloc_from_file(size, mr, share, path, errp); //实体MR指向的RAM_BLOCK为qemu_ram_alloc_from_file函数返回的RAMBlock
mr->dirty_log_mask = tcg_enabled() ? ( << DIRTY_MEMORY_CODE) : ;
}
#endif

函数 qemu_ram_alloc_from_file()中申请并设置RAMBlock,RAMBlock->host 为函数file_ram_alloc()函数的返回值,该函数使用对应路径的(设备)文件来分配内存,调用qemu_ram_mmap()通过mmap方式进行内存分配,可见RAMBlock->host 则为分配的内存的hva的起始地址。

static void *file_ram_alloc(RAMBlock *block,
ram_addr_t memory,
const char *path,
Error **errp)
{
......
area = qemu_ram_mmap(fd, memory, block->mr->align,
block->flags & RAM_SHARED);//通过mmap在qemu的进程地址空间中进行地址分配
if (area == MAP_FAILED) {
error_setg_errno(errp, errno,
"unable to map backing store for guest RAM");
goto error;
}

上述为对实体MemoryRegion “pc.ram” 内存的分配,在为别名MemoryRegion“ram-below-4g”和“ram-above-4g”初始化时调用的是函数memory_region_init_alias(), 该函数调用memory_region_init()

void memory_region_init(MemoryRegion *mr,
Object *owner,
const char *name,
uint64_t size)
{
object_initialize(mr, sizeof(*mr), TYPE_MEMORY_REGION);
mr->size = int128_make64(size);
if (size == UINT64_MAX) {
mr->size = int128_2_64();
}
mr->name = g_strdup(name);
mr->owner = owner;
mr->ram_block = NULL; //别名MR的ram_block设置为null
.......
}

在该函数中将别名MR的ram_block设置为NULL,而“pc.ram”指向的ram_block是有内容的,可见不是所有的MemoryRegion都有对应的RAMBlock,对于分配的RAMBlock,最后会将其插入到全局链表RAMList中。

上述对结构体MemoryRegion和RAMblock的分析可知,对于系统内存而言(不考虑io)实体MemoryRegion是有具体内存的,而别名MemoryRegion是对实体MR不同分段的一个指向,其alias指向实体MR。别名MR都是根级MR system_memory的subregion,通过RAMBlock,可以知道一个MemoryRegion对应内存的hva,其关系大致如下:

三、AddressSpace

从GPA与hva的角度来看,如果以结构体MemoryRegion为核心的话,RAMBlock可以看成是对该片内存区域hva的关联,而AddressSpace在我看来可以看做是对该片内存区域GPA的一个关联,从其注释AddressSpace: describes a mapping of addresses to #MemoryRegion objects也可看出。

这里我有一个疑问:在qemu-2.3.0版本的源码中,结构体MemoryRegion中有一个变量ram_addr表示该片内存区域的GPA的起始地址,而在qemu-2.8.0中,结构体MemoryRegion中没有了这个变量。猜想对于实体MR而言,其addr变量是否就表示为该片内存区域GPA的起始地址,如果是的话,那么对于subregion而言,其alias_offset加上实体addr即可表示该片MemoryRegion的GPA起始地址,加上实体MR对应的RAMBlock,应该就可以实现GPA到HVA的映射了,那么AddressSpace的作用又是什么,其意义何在?先提出这个疑问,看看后续能否得到解答。

/**
* AddressSpace: describes a mapping of addresses to #MemoryRegion objects
*/
struct AddressSpace {
/* All fields are private. */
struct rcu_head rcu;
char *name;
MemoryRegion *root; //指向根MR
int ref_count;
bool malloced; /* Accessed via RCU. */
struct FlatView *current_map; // 指向当前维护的 FlatView,在 address_space_update_topology 时作为 old 比较 int ioeventfd_nb;
struct MemoryRegionIoeventfd *ioeventfds;
struct AddressSpaceDispatch *dispatch; // 负责根据 GPA 找到 HVA
struct AddressSpaceDispatch *next_dispatch;
MemoryListener dispatch_listener;
QTAILQ_HEAD(memory_listeners_as, MemoryListener) listeners;
QTAILQ_ENTRY(AddressSpace) address_spaces_link;
};

结构体AddressSpace用来表示虚拟机的一片地址空间,不同的设备使用的地址空间不同,但qemu x86中只有两种, address_space_memory和address_space_io,这也是两个全局的address_space变量,所有设备的地址空间都被映射到了这两个上面。其root指向根MemoryRegion, 对于全局变量address_space_memory而言,其root指向系统全局的system_memory,address_space_io的root则指向system_io.由于根MR可能有自己的若干个subregion,因此每个AddressSpace一般包含一系列MemoryRegion,形成树状结构。

AddressSpace中的current_map指向当前维护的FlatView:

/*
* Note that signed integers are needed for negative offsetting in aliases
* (large MemoryRegion::alias_offset).
*/
struct AddrRange {
Int128 start; //起始
Int128 size; //大小
}; /* Range of memory in the global map. Addresses are absolute. */
struct FlatRange {
MemoryRegion *mr; //指向所属的MR
hwaddr offset_in_region; //在MR中的offset
AddrRange addr; //本FR代表的区间
uint8_t dirty_log_mask;
bool romd_mode;
bool readonly;
}; /* Flattened global view of current active memory hierarchy. Kept in sorted
* order.
*/
struct FlatView {
struct rcu_head rcu;
unsigned ref; //引用计数,为0就销毁
FlatRange *ranges; //对应的flatrange数组
unsigned nr; //flatrange数目
unsigned nr_allocated;
};

FlatView管理MR展开后得到的所有FlatRange,ranges是一个数组,记录FlatView下所有的FlatRange,每个FlatRange对应一段虚拟机物理地址区间,各个FlatRange不会重叠,按照地址的顺序保存在数组中。具体的范围由一个AddrRange结构描述,其描述了地址和大小。当memory region发生变化的时候,执行memory_region_transaction_commit,address_space_update_topology,address_space_update_topology_pass最终完成更新FlatView的目标。

FlatView结构如下,图源见水印:

由图片可知每个FlatRange的中的AddrRange的start为该段内存区间GPA的首地址,size则描述了该段区间的大小。那么结构体FlatRange中的offset_in_region是什么,是该flatrange相对于所属MR的offset?

与flatrange对应的是MemoryRegionSection:

/**
* MemoryRegionSection: describes a fragment of a #MemoryRegion
*
* @mr: the region, or %NULL if empty
* @address_space: the address space the region is mapped in
* @offset_within_region: the beginning of the section, relative to @mr's start
* @size: the size of the section; will not exceed @mr's boundaries
* @offset_within_address_space: the address of the first byte of the section
* relative to the region's address space
* @readonly: writes to this section are ignored
*/
struct MemoryRegionSection {
MemoryRegion *mr; // 指向所属 MemoryRegion
AddressSpace *address_space; // 所属 AddressSpace
hwaddr offset_within_region; // 起始地址 (HVA) 在 MemoryRegion 内的偏移量
Int128 size;
hwaddr offset_within_address_space; // 在 AddressSpace 内的偏移量,如果该 AddressSpace 为系统内存,则为 GPA 起始地址
bool readonly;
};

MemoryRegionSection 指向 MemoryRegion 的一部分 ([offset_within_region, offset_within_region + size]),是注册到 KVM 的基本单位。

将 AddressSpace 中的 MemoryRegion 映射到线性地址空间后,由于重叠的关系,原本完整的 region 可能会被切分成片段,于是产生了 MemoryRegionSection。

其中偏移offset_within_region描述的是该section在其所属的MR中的偏移,一个address_space可能有多个MR构成,因此该offset是局部的。而offset_within_address_space是在整个地址空间中的偏移,是全局的offset,如果AddressSpace为系统内存,则该偏移则为GPA的起始地址。

到这里,借助函数kvm_set_phys_mem()中组装kvmslot,并通过kvm_userspace_memory_region将qemu的内存分布信息传递给kvm的部分过程整理一下上述数据结构中GPA到HVA的对应关系:

static void kvm_set_phys_mem(KVMMemoryListener *kml,
MemoryRegionSection *section, bool add)
{
KVMState *s = kvm_state;
KVMSlot *mem, old;
int err;
MemoryRegion *mr = section->mr;
bool writeable = !mr->readonly && !mr->rom_device;
hwaddr start_addr = section->offset_within_address_space; //获取GPA
ram_addr_t size = int128_get64(section->size);
void *ram = NULL;
unsigned delta; /* kvm works in page size chunks, but the function may be called
with sub-page size and unaligned start address. Pad the start
address to next and truncate size to previous page boundary. */
delta = qemu_real_host_page_size - (start_addr & ~qemu_real_host_page_mask);
delta &= ~qemu_real_host_page_mask;
if (delta > size) {
return;
}
start_addr += delta; //页对齐修正
size -= delta;
size &= qemu_real_host_page_mask;
if (!size || (start_addr & ~qemu_real_host_page_mask)) {
return;
} if (!memory_region_is_ram(mr)) {
if (writeable || !kvm_readonly_mem_allowed) {
return;
} else if (!mr->romd_mode) {
/* If the memory device is not in romd_mode, then we actually want
* to remove the kvm memory slot so all accesses will trap. */
add = false;
}
} ram = memory_region_get_ram_ptr(mr) + section->offset_within_region + delta; //获取hva
.......
}

GPA:在该函数中传入的参数为MemoryRegionSection,根据region section在AddressSpace中的偏移,即offset_within_address_space,加上页对齐修正(delta)得到该section的GPA,填入start_addr。

HVA: hva是通过该section所属的MR的起始HVA + 该region section在所属MR中的偏移量(offset_within_region)+页对齐修正(delta)得到。

该region section所属MR的起始HVA通过函数memory_region_get_ram_ptr()得到,该函数内容如下:

void *memory_region_get_ram_ptr(MemoryRegion *mr)
{
void *ptr;
uint64_t offset = ; rcu_read_lock();
while (mr->alias) { //追溯到实体MR为止
offset += mr->alias_offset;
mr = mr->alias;
}
assert(mr->ram_block);
ptr = qemu_map_ram_ptr(mr->ram_block, offset); //实体MR有对应的RAMBlock
rcu_read_unlock(); return ptr;
} void *qemu_map_ram_ptr(RAMBlock *ram_block, ram_addr_t addr)
{
RAMBlock *block = ram_block; if (block == NULL) {
block = qemu_get_ram_block(addr);
addr -= block->offset;
} if (xen_enabled() && block->host == NULL) {
/* We need to check if the requested address is in the RAM
* because we don't want to map the entire memory in QEMU.
* In that case just map until the end of the page.
*/
if (block->offset == ) {
return xen_map_cache(addr, , );
} block->host = xen_map_cache(block->offset, block->max_length, );
}
return ramblock_ptr(block, addr);
} static inline void *ramblock_ptr(RAMBlock *block, ram_addr_t offset)
{
assert(offset_in_ramblock(block, offset));
return (char *)block->host + offset; //hva的起始地址加上所有偏移得到最终hva
}

在 memory_region_get_ram_ptr 中,如果当前MR是另一个MR的 alias,则会向上追溯,一直追溯到非 alias region(实体 region) 为止。将追溯过程中的 alias_offset 加起来,可以得到当前 region 在实体 region 中的偏移量。由于实体 region 具有对应的 RAMBlock,所以调用函数 qemu_map_ram_ptr ,将实体 region 对应的 RAMBlock 的 host 和总 offset 加起来,得到当前 region 的起始 HVA。

在函数qemu_map_ram_ptr()中,如果传入的ram_block为空,还可以根据当前region在实体region中的偏移量找到对应的ramblock,其调用qemu_get_ram_block()

static RAMBlock *qemu_get_ram_block(ram_addr_t addr)
{
RAMBlock *block; block = atomic_rcu_read(&ram_list.mru_block);//首先看是不是处于最近使用的block中
if (block && addr - block->offset < block->max_length) { //addr即为当前region相对于实体region的offset,若offset-当前block.offset小于该block的大小,说明该region对应的内存处于该block中
return block;
}
QLIST_FOREACH_RCU(block, &ram_list.blocks, next) { //不在最近使用的block中,则遍历RAMList的所有block
if (addr - block->offset < block->max_length) {
goto found;
}
} fprintf(stderr, "Bad ram offset %" PRIx64 "\n", (uint64_t)addr);
abort();
.......
}

由于每一段内存都对应一个RAMblock,通过当前region相对于实体region的offset可以知道这段内存的大小,如果该段大小减去某个RAMBlock的offset小于该block的size,说明该段内存对应的hva在这段block中,否则则查找下一个。比如第一个block的offset为0,如果addr小于该block的大小,那么该block就是这段内存区域对应的block。

一些猜想及疑问:

1、虚拟机的GPA是从0开始的,由系统内存的初始化过程可以看出(不考虑io),初始时分配了一整片内存“pc.ram”及对应的RAMBlock,因此猜想MemoryRegion “pc.ram”的addr为起始GPA,即为0,其他region到该实体region的各级alias_offset之和应该就是该region的起始GPA。又MemoryRegionSection中的offset_within_address_space表示在所属AddressSpace中的偏移量,若该AS为系统内存,则为GPA的起始地址。那么各级subregion的alias_offset相加,再加上实体MR的addr是否就等于MemoryRegionSection中的offset_within_address_space。个人感觉应该是,但不确定,可通过实验进行相关验证。

2、若上述猜想是对的,那么由MemoryRegion及RAMBlock即可得到GPA到HVA的对应关系,那么之前提出的疑问:AddressSpace的意义何在?分析qemu的源码可知AddressSpace绑定了相关listener,当发生变化时会触发相关的listener,不能单从GPA到HVA的映射来考虑AddressSpace的意义。两个全局的AddressSpace(address_space_io,address_space_memory)串起了属于系统内存和io内存的所有memoryRegion,当内存发生变化时,会触发相关listener。所以个人认为AddressSpace可以更好地对不同级别的MemoryRegion进行管理,而不需要为各个MemoryRegion注册绑定listener。且由源码可以看出,MemoryRegion的偏移更偏向于应用得到该region对应于起始hva的偏移,从而计算该region的起始hva,而AddressSpace更偏向于应用于得到起始GPA。(若实体MR的addr为起始GPA,那么该MR到实体MR的偏移之和也可以用于得到该region的起始GPA,但源码中并没有应用此种方式,因为AddressSpace中的相关变量已经可以表示起始GPA了)

3、结构体FlatRange.addr.start就可以表示该段FlatRange的起始GPA,那么该结构体中的offset_in_region是什么,是其相对于所属的MR的offset,其意义又是什么?该问题从函数listener_add_address_space()中可以得到一些解答:

static void listener_add_address_space(MemoryListener *listener,
AddressSpace *as)
{
FlatView *view;
FlatRange *fr;
....... view = address_space_get_flatview(as);//获取as中的flatview
FOR_EACH_FLAT_RANGE(fr, view) { //遍历flatview中的每个flatrange
MemoryRegionSection section = { //新建一个memoryregionsection 并进行赋值
.mr = fr->mr,
.address_space = as,
.offset_within_region = fr->offset_in_region,
.size = fr->addr.size,
.offset_within_address_space = int128_get64(fr->addr.start),
.readonly = fr->readonly,
};
......
}

由上述代码也可以看出FlatRange和MemoryRegionSection的对应关系,MemoryRegionSection中的offset_within_region即为FlatRange的offset_in_region,因此均表示为在所属MR中的偏移,若所属MR为全局MR,则表示为在全局MR中的偏移。同样的,MemoryRegionSection中的offset_within_address_space即为FlatRange.addr.start,表示GPA的起始地址。

补充一个在虚拟机退出时如何根据GPA找到HVA:https://www.anquanke.com/post/id/86412 链接中的第四小节对此进行了分析,主要原理是由AddressSpaceDispatch中的6级页表PhysPageMap实现,该页表的最后一级指向MemoryRegionSection,由MemoryRegionSection可以得到GPA对应的MR,由此得到HVA。

后续会分析结构体AddressSpace注册的listerner的一些操作,以及qemu如何把内存管理的信息传至KVM中,以及如何进行视图的更新。

以上仅是对qemu中管理虚拟机内存的一些数据结构的整理,由于个人理解及分析不够,存在着一些疑问及猜想,难免有不对的地方,欢迎大家提出疑问,指正错误。

参考:https://www.cnblogs.com/ck1020/p/6729224.html

https://www.binss.me/blog/qemu-note-of-memory/

http://oenhan.com/qemu-memory-struct

https://blog.csdn.net/leoufung/article/details/48781205

qemu对虚拟机的内存管理(一)的更多相关文章

  1. qemu对虚拟机的内存管理(二)

    上篇文章主要分析了qemu中对虚拟机内存管理的关键数据结构及他们之间的联系,这篇文章则主要分析在地址空间发生变化时,如何将其更新至KVM中,保持用户空间与内核空间的同步. 这一系列操作与之前说的Add ...

  2. 深入java虚拟机学习 -- 内存管理机制

    前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思 ...

  3. 深入了解Java虚拟机和内存管理

    1.java程序的执行过程      java源文件->解析器->class文件->java类加载器->java运行时数据区->执行引擎 2.我们接下来看一下java运行 ...

  4. [深入理解Java虚拟机]<自动内存管理>

    Overview 走近Java:介绍Java发展史 第二部分:自动内存管理机制 程序员把内存控制的权利交给了Java虚拟机,从而可以在编码时享受自动内存管理.但另一方面一旦出现内存泄漏和溢出等问题,就 ...

  5. Dalvik虚拟机的内存管理

    Dalvik虚拟机的内存分为三种类型: Java Object Heap,    Bitmap Memory,   Native Heap.    下面,就这三种类型进行详细讲解:   一.    J ...

  6. Java虚拟机的内存管理

    众所周知,Java程序员写的代码是没有办法控制Java对象的内存释放的,完全有JVM暗箱操作. 虽然程序员把内存的释放的任务都交给了Java虚拟机,但是并不代表Java程序就不存在内存泄漏. 反而,某 ...

  7. 深入理解java虚拟机,内存管理部分

    1,对象回收前会调用finalize()方法,尝试自救,只能调用一次 2,上面横向对比c++的析构函数,但是java有良好的内存管理,而且try/catch做得比较好 3,回收一个常量,1,对象的实例 ...

  8. java虚拟机(一)——内存管理机制与OOM异常

    一  java内存区域与内存溢出异常(OOM) 1)运行时数据区域划分        1.程序计数器(Program Conuter Register) 程序计数器是一块较小的内存空间,它是当前线程执 ...

  9. 深入理解JAVA虚拟机 自动内存管理机制

    运行时数据区域 其中右侧三个一起的部分是每个线程一份,左侧两个是所有线程共享的. 程序计数器(Program Counter Register) 英文名称叫Program Counter Regist ...

随机推荐

  1. Atitit sleep原理 node.js sleep解决方案 timer

    Atitit  sleep原理  node.js sleep解决方案  timer sleep()的实现分为三步: 1.注册一个信号signal(SIGALRM,handler).接收内核给出的一个信 ...

  2. idea当配置eclipse快捷键时,全局替换的快捷键是什么?

    简介   每次为了新版本新建一个分支的时候,都要改每个maven的版本号,好麻烦,有么有?如下图: 当idea配置eclipse快捷键时,默认是没有全局替换快捷键的,需要设置 步骤 首先打开setti ...

  3. AI人工智能顶级实战工程师 课程大纲

    课程名称    内容    阶段一.人工智能基础 — 高等数学必知必会     1.数据分析    "a. 常数eb. 导数c. 梯度d. Taylore. gini系数f. 信息熵与组合数 ...

  4. 我们在学习JDBC的时候会过度到J2EE。

    我们在学习JDBC的时候会过度到J2EE.   在Swing的组件中,基本上都是在AWT组件的名称前面加“J”. 一般情况下,除了Choise等组件: import javax.swing.*;好要加 ...

  5. Netty HTTP 服务端入门开发

    一. HTTP 简介 HTTP(超文本传输协议) 协议是建立在 TCP 传输协议之上的应用层协议,它的发展是万维网协会和 Internet 工作小组和 IETF 合作的结果. HTTP 是一个属于应用 ...

  6. Linux 用户管理_015

    一.用户基础了解 Linux是一个多任务.多用户的操作系统,每个用户和进程都需要对应一个用户和用户组,用户名相当于姓名, 用户UID相当于身份证号,用户组GID相当于公司的工号.用户与用户组的关系一对 ...

  7. linux java 安装

    对于java的开发,有openJDK 和 orcale jdk两种,大多数的linux 系统都会内置openjdk的安装包,但是大多数java项目的开发都是基于orcale jdk的,所以安装orca ...

  8. 聊聊IOCP,聊聊异步编程

    *:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...

  9. SQL server 在附加数据库后,数据库总是变成了只读

    1.  要把数据库文件的属性改了 右键点击两个文件的属性--安全--添加--立即查找--找everyone这个用户把他的权限都勾上 确定再附加就OK. 2. 在数据库管理器中对数据库点右键属性,然后切 ...

  10. macOS Sierra 10.12.6 odoo 10.0 开发环境配置

    参考了网上的一些教程,将最终过程记录一下,其中需要的一些程序,我已经放到百度网盘: https://pan.baidu.com/s/1boKHSTL 1.抹盘安装macOS Sierra 10.12. ...