1 Qemu內存分佈
2 內存初始化
Qemu中的內存模型,簡單來講就是Qemu申請用戶態內存並進行管理,並將該部分申請的內存註冊到對應的加速器(如KVM)中。這樣的模型有以下好處:node
兼容其餘加速器模型(或者無加速器,單純使用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();
}app
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");ide
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註冊ui
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)
該函數實現虛擬機的物理地址到主機虛擬地址的轉換spa