标签:state list node 防止 一段 link arc 特殊 加速器
1 Qemu内存分布兼容其他加速器模型(或者无加速器,单纯使用Qemu做模拟)
Qemu需要做的有两方面工作:向KVM注册用户态内存空间,申请用户态内存空间。
Qemu主要通过如下结构来维护内存:
/ A system address space - I/O, memory, etc. /
struct AddressSpace {
char name;
MemoryRegion root;
FlatView current_map;
int ioeventfd_nb;
MemoryRegionIoeventfd ioeventfds;
struct AddressSpaceDispatch dispatch;
struct AddressSpaceDispatch next_dispatch;
MemoryListener dispatch_listener;
QTAILQ_ENTRY(AddressSpace) address_spaces_link;
};
"memory"的root是static MemoryRegion system_memory;
使用链表address_spaces保存虚拟机的内存,该链表保存AddressSpace address_space_io和AddressSpace address_space_memory等信息
void address_space_init(AddressSpace as, MemoryRegion root, const char *name)
{
if (QTAILQ_EMPTY(&address_spaces)) {
memory_init();
}
memory_region_transaction_begin();
as->root = root;
as->current_map = g_new(FlatView, 1);
flatview_init(as->current_map);
as->ioeventfd_nb = 0;
as->ioeventfds = NULL;
QTAILQ_INSERT_TAIL(&address_spaces, as, address_spaces_link);
as->name = g_strdup(name ? name : "anonymous");
address_space_init_dispatch(as);
memory_region_update_pending |= root->enabled;
memory_region_transaction_commit();
}
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");
memory_listener_register(&core_memory_listener, &address_space_memory);
}
AddressSpace设置了一段内存,其主要信息存储在root成员 中,root成员是个MemoryRegion结构,主要存储内存区的结构。在Qemu中最主要的两个AddressSpace是 address_space_memory和address_space_io,分别对应的MemoryRegion变量是system_memory和 system_io。
Qemu的主函数是vl.c中的main函数,其中调用了configure_accelerator(),是KVM初始化的配置部分。
configure_accelerator中首先根据命令行输入的参数找到对应的accelerator,这里是KVM。之后调用accel_list[i].init(),即kvm_init()。
在kvm_init()函数中主要做如下几件事情:
3 内存分配
内存的分配实现函数为 ram_addr_t qemu_ram_alloc(ram_addr_t size, MemoryRegion *mr),输出为该次分配的内存在所有分配内存中的顺序偏移(即下图中的红色数字).
该函数最终调用phys_mem_alloc分配内存, 并将所分配的全部内存块, 串在一个ram_blocks开头的链表中, 如下示意:
上图中分配了4个内存块, 每次分配时偏移offset顺序累加, host指向该内存块在主机中的虚拟地址.
调用memory_listener_register注册
4 内存映射
使用的相关结构体如下:
/ Range of memory in the global map. Addresses are absolute. /
struct FlatRange {
MemoryRegion mr;
hwaddr offset_in_region;
AddrRange addr;
uint8_t dirty_log_mask;
bool romd_mode;
bool readonly;
};
/ Flattened global view of current active memory hierarchy. Kept in sorted order./
struct FlatView {
unsigned ref;
FlatRange ranges;
unsigned nr;
unsigned nr_allocated;
};
映射是将上面分配的地址块映射为客户机的物理地址, 函数如下, 输入为映射后的物理地址, 内存偏移,通用内存块的地址
static void memory_region_add_subregion_common(MemoryRegion mr, hwaddr offset, MemoryRegion subregion)
MemoryRegion mr:对应的是system_memory或者system_io,通过memory_listener_register函数注册内存块。
通用栈如下:
memory_region_update_container_subregions
memory_region_transaction_commit
address_space_update_topology
generate_memory_topology
address_space_update_topology_pass
memory_region_update_container_subregions函数在链表中寻找合适的位置插入,
/插入指定的位置/
QTAILQ_FOREACH(other, &mr->subregions, subregions_link) {
if (subregion->priority >= other->priority) {
QTAILQ_INSERT_BEFORE(other, subregion, subregions_link);
goto done;
}
}
QTAILQ_INSERT_TAIL(&mr->subregions, subregion, subregions_link);
memory_region_transaction_commit中引入了新的结构address_spaces(AS),内存有不同的应用类型,address_spaces以链表形式存在,commit函数则是对所有AS执行 address_space_update_topology,先看AS在哪里注册的,就是前面提到的kvm_init里面,执行 memory_listener_register,注册了address_space_memory和address_space_io两个,涉及的另 外一个结构体则是MemoryListener,有kvm_memory_listener和kvm_io_listener,就是用于监控内存映射关系 发生变化之后执行回调函数。
address_space_update_topology_pass函数比较之前的内存块,做相应的处理
MEMORY_LISTENER_UPDATE_REGION函数,将变化的FlatRange构造一个MemoryRegionSection,然后 遍历所有的memory_listeners,如果memory_listeners监控的内存区域和MemoryRegionSection一样,则执 行第四个入参函数,如region_del函数,即kvm_region_del函数,这个是在kvm_init中初始化的。 kvm_region_add主要是kvm_set_phys_mem函数,主要是将MemoryRegionSection有效值转换成KVMSlot 形式,在kvm_set_user_memory_region中使用kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem)传递给kernel。
5 客户机物理地址到主机虚拟地址的转换
5.1 地址属性
内存映射是以页为单位的, 也就意味着phys_offset的低12bit为0, Qemu使用这些bit标识地址属性:
Bit 11-3 Bit 2 Bit 1 Bit 0
MMIO索引, 其中4个固定分配 SUBWIDTH SUBPAGE ROMD
0: RAM
1: ROM
2: UNASSIGNED
3: NOTDIRTY
5.2 客户机物理地址到主机虚拟地址的转换步骤
虚拟机因mmio退出时,qemu处理该退出事件,相关的函数:
void cpu_physical_memory_rw(hwaddr addr, uint8_t buf, int len, int is_write)
该函数实现虚拟机的物理地址到主机虚拟地址的转换
标签:state list node 防止 一段 link arc 特殊 加速器
原文地址:http://blog.51cto.com/zybcloud/2149626