QEMU下的内存结构MemoryRegion和AddressSpace
QEMU下的内存结构体很多了,RAMBlock,MemoryRegion,AddressSpace,MemoryRegionSection,KVMSlot和kvm_userspace_memory_region,很多时候看代码时候都会被搅合成一坨,虽然很早时候在KVM源代码分析2:虚拟机的创建与运行和KVM源代码分析4:内存虚拟化都提到了内存虚拟化的过程,但实际上这几个结构体之间的关系并没有理清,外加上lizhen童鞋要求分析讲一讲,故在此处补个补丁。
QEMU代码:git v2.8.0
RAMBLOCK才是真正分配了host内存的地方,如果把它直接理解成一个内存条也是非常合适的,但实际上不仅仅如此,还有设备自有内存,显存。它的主要元素就是mr,host, offset和used_length。
struct RAMBlock { struct rcu_head rcu; struct MemoryRegion *mr; uint8_t *host; ram_addr_t offset; ram_addr_t used_length; ram_addr_t max_length; void (*resized)(const char*, uint64_t length, void *host); uint32_t flags; /* Protected by iothread lock. */ char idstr[256]; /* RCU-enabled, writes protected by the ramlist lock */ QLIST_ENTRY(RAMBlock) next; int fd; size_t page_size; };
每个RAMBLOCK都有一个唯一的MemoryRegion对应,另外需要注意的是不是每个MemoryRegion都有RAMBLOCK对应的。host则是host的线性地址
new_block->host = phys_mem_alloc(new_block->max_length, &new_block->mr->align);
offset则是这个block在ram中的base地址,如qemu_get_ram_block下的代码
block = atomic_rcu_read(&ram_list.mru_block); if (block && addr - block->offset < block->max_length) { return block; }
offset则是block在ram中的偏移位置。
然后看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; Object *owner; const MemoryRegionIOMMUOps *iommu_ops; const MemoryRegionOps *ops; void *opaque; MemoryRegion *container; 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(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是树状父子结构的,每一个ramblock都有一个对应的MemoryRegion,一般而言,这个MemoryRegion是最顶级的MemoryRegion,它还有很多子MemoryRegion,比如在这个ramblock地址范围内的MMIO等,这里面的重点元素是size,addr和alias_offset,
memory_region_init_alias(ram_below_4g, NULL, "ram-below-4g", ram, 0, pcms->below_4g_mem_size); memory_region_init(mr, owner, name, size); mr->size = int128_make64(size); mr->alias_offset = offset;
addr的由来则是
memory_region_add_subregion(system_memory, 0x100000000ULL,ram_above_4g); memory_region_add_subregion_common(mr, offset, subregion); subregion->addr = offset;
此处的addr实际也是mr的起始地址,和offset有点类似
struct AddressSpace { /* All fields are private. */ struct rcu_head rcu; char *name; MemoryRegion *root; int ref_count; bool malloced; /* Accessed via RCU. */ struct FlatView *current_map; int ioeventfd_nb; struct MemoryRegionIoeventfd *ioeventfds; struct AddressSpaceDispatch *dispatch; struct AddressSpaceDispatch *next_dispatch; MemoryListener dispatch_listener; QTAILQ_HEAD(memory_listeners_as, MemoryListener) listeners; QTAILQ_ENTRY(AddressSpace) address_spaces_link; };
地址空间,本来不同的设备使用的地址空间不同,但是QEMU X86里面只有两种,address_space_memory和address_space_io,所有设备的地址空间都被映射到了这两个上面,
struct FlatRange { MemoryRegion *mr; hwaddr offset_in_region; AddrRange addr; uint8_t dirty_log_mask; bool romd_mode; bool readonly; }; struct FlatView { struct rcu_head rcu; unsigned ref; FlatRange *ranges; unsigned nr; unsigned nr_allocated; };
是通过FlatView体现的,当memory region发生变化的时候,执行memory_region_transaction_commit,address_space_update_topology,address_space_update_topology_pass最终完成更新FlatView的目标。
FlatView结构如下,图片来自刘峰童鞋。
每一个flat range都投射到同一个地址空间的平面上,而上图中的R1,R2等对应的则是struct MemoryRegionSection。
struct MemoryRegionSection { MemoryRegion *mr; AddressSpace *address_space; hwaddr offset_within_region; Int128 size; hwaddr offset_within_address_space; bool readonly; };
在下面被调用
#define MEMORY_LISTENER_UPDATE_REGION(fr, as, dir, callback, _args...) \ do { \ MemoryRegionSection mrs = section_from_flat_range(fr, as); \ MEMORY_LISTENER_CALL(as, callback, dir, &mrs, ##_args); \ } while(0)
在MEMORY_LISTENER_CALL中调用kvm_region_add和kvm_region_del,执行kvm_set_phys_mem,组装KVMSlot
typedef struct KVMSlot { hwaddr start_addr; ram_addr_t memory_size; void *ram; int slot; int flags; } KVMSlot;
最终通过kvm_userspace_memory_region将QEMU的内存分布信息传递给KVM。
具体元素的关系,见下图
QEMU下的内存结构MemoryRegion和AddressSpace来自于OenHan
链接为:https://oenhan.com/qemu-memory-struct