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/