QEMU之内存虚拟化

内存虚拟化方案

最直观的方案,将QEMU进程的虚拟地址空间的一部分作为虚拟机的物理地址。但该方案有一个问题:

在这里插入图片描述

在物理机上,CPU对内存的访问在保护模式下是通过分段分页实现的,在该模式下,CPU访问时使用的是虚拟地址,必须通过硬件MMU进行转换,将虚拟地址转换成物理地址才能够访问到实际的物理内存:

虚拟地址
物理地址
CPU
MMU
物理地址

显然,在虚拟机内部,其也是访问的虚拟地址,要想让其访问到实际的物理内存,必须先将这个地址转换成虚拟机的物理地址,然后将物理地址转换成QEMU的虚拟地址,最后将QEMU的虚拟地址转换成物理机上的物理地址,才能访问到数据。这就引出MMU的虚拟化

EPT(Extended Page Table),VMX架构引入了EPT(Extended Page Table, 扩展页表)机制来实现VM物理地址空间的隔离, EPT机制实现原理与x86/x64的分页机制是一致的。当guest软件发出指令访问内存时, guest最终生成GPA(Guest-Physical Address)。EPT页表结构定义在host端, 处理器接受到guest传来guest-physical address后, 通过EPT页表结构转换为HPA(Host-physical address), 从而访问平台上的物理地址。

纯软MMU

在CPU没有支持EPT之前,通过影子页表(Shadow Page Table)来实现虚拟机地址到host主机物理地址的转换。效率较低,KVM中维护影子页表。

影子页表
GVA
HPA
支持EPT

EPT方案中,CPU的寻址模式在VM non-root operation下会发生变化,其会使用两个页表。

在这里插入图片描述

如果开启EPT,当CPU进行VM Entry时,会使用EPT功能,虚拟机对内部自身页表有着完全的控制,CPU先将虚拟机内部的虚拟地址转换为虚拟机物理地址,通过这个过程查找虚拟机内部页表,然后CPU会将这个地址转换为宿主机的物理地址,通过这个过程查找宿主机中的EPT页表。当CPU产生VM Exit时,EPT会关闭,这个时候CPU在宿主机上又会按照传统的单页表方式寻址。虚拟机中的页表就像物理机操作系统页表一样,页表里面保存的是虚拟机物理地址,由自己维护,EPT页表则由宿主机维护,里面记录着虚拟机物理地址到宿主机的物理地址的转换。

QEMU内存虚拟化初始化

基本数据结构

AddressSpace结构体,用来表示一个虚拟机或者虚拟CPU能够访问的所有物理地址。注意这里的访问和能够访问是两回事,与进程的地址空间一样,一个进程的虚拟地址空间为4GB(32位下),这并不是说操作系统需要为进程分配这么大的空间。同样,QEMU中的AddressSpace表示的是一段地址空间,整个系统可以有一个全局的地址空间,CPU可以有自己的地址空间视角,设备也可以有自己的地址空间视角。

/*** struct AddressSpace: describes a mapping of addresses to #MemoryRegion objects*/
struct AddressSpace {/* private: */struct rcu_head rcu;char *name;MemoryRegion *root;/* Accessed via RCU.  */struct FlatView *current_map;int ioeventfd_nb;struct MemoryRegionIoeventfd *ioeventfds;QTAILQ_HEAD(, MemoryListener) listeners;QTAILQ_ENTRY(AddressSpace) address_spaces_link;
};

QEMU的其他子系统可以注册地址空间变更的事件,所有注册的信息都通过listeners连接起来。所有的AddressSpace通过address_spaces_link这个node连接起来,链表头是address_spaces。

内存管理中另一个结构是MemoryRegion,它表示的是虚拟机的一段内存区域。MemoryRegion是内存模拟中的核心结构,整个内存的模拟都是通过MemoryRegion构成的无环图完成的,图的叶子节点是实际分配给虚拟机的物理内存或者是MMIO,中间的节点则表示内存总线,内存控制器是其他MemoryRegion的别名。

在这里插入图片描述

struct MemoryRegion {Object parent_obj;/* private: *//* The following fields should fit in a cache line */bool romd_mode;bool ram;bool subpage;bool readonly; /* For RAM regions */bool nonvolatile;bool rom_device;bool flush_coalesced_mmio;uint8_t dirty_log_mask;bool is_iommu;RAMBlock *ram_block;Object *owner;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(, MemoryRegion) subregions;QTAILQ_ENTRY(MemoryRegion) subregions_link;QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;const char *name;unsigned ioeventfd_nb;MemoryRegionIoeventfd *ioeventfds;RamDiscardManager *rdm; /* Only for RAM */
};
QEMU虚拟内存初始化

虚拟机虚拟化内存是在/hw/i386/pc_piix.c中进行的,内存分为低端内存和高端内存,之所以会有这个区分是因为一些有传统设备的虚拟机,其设备必须使用一些地址空间在4GB以下的内存。

/* PC hardware initialisation */
static void pc_init1(MachineState *machine,const char *host_type, const char *pci_type)
{...if (!pcms->max_ram_below_4g) {pcms->max_ram_below_4g = 0xe0000000; /* default: 3.5G */}lowmem = pcms->max_ram_below_4g;if (machine->ram_size >= pcms->max_ram_below_4g) {if (pcmc->gigabyte_align) {if (lowmem > 0xc0000000) {lowmem = 0xc0000000;}if (lowmem & (1 * GiB - 1)) {warn_report("Large machine and max_ram_below_4g ""(%" PRIu64 ") not a multiple of 1G; ""possible bad performance.",pcms->max_ram_below_4g);}}}if (machine->ram_size >= lowmem) {x86ms->above_4g_mem_size = machine->ram_size - lowmem;x86ms->below_4g_mem_size = lowmem;} else {x86ms->above_4g_mem_size = 0;x86ms->below_4g_mem_size = machine->ram_size;}...
}

KVM内存虚拟化

虚拟机MMU初始化

VMCS中VM execution区域里的secondary processor-based VM-execution control字段的第二位用来表示是是否开启EPT,在KVM初始化的时候会调用架构相关的hardware_setup函数,hardware_setup函数会调用setup_vmcs_config,在其中读取MSR_IA32_VMX_PROCBASED_CTLS2,将寄存器存放在vmcs_conf->cpu_based_2nd_exec_ctrl中。

KVM在创建VCPU的过程中会创建虚拟机MMU,具体是在函数kvm_arch_vcpu_init中调用kvm_mmu_create。

int kvm_mmu_create(struct kvm_vcpu *vcpu)
{uint i;int ret;vcpu->arch.mmu_pte_list_desc_cache.kmem_cache = pte_list_desc_cache;vcpu->arch.mmu_pte_list_desc_cache.gfp_zero = __GFP_ZERO;vcpu->arch.mmu_page_header_cache.kmem_cache = mmu_page_header_cache;vcpu->arch.mmu_page_header_cache.gfp_zero = __GFP_ZERO;vcpu->arch.mmu_shadow_page_cache.gfp_zero = __GFP_ZERO;vcpu->arch.mmu = &vcpu->arch.root_mmu;vcpu->arch.walk_mmu = &vcpu->arch.root_mmu;vcpu->arch.root_mmu.root_hpa = INVALID_PAGE;vcpu->arch.root_mmu.root_pgd = 0;vcpu->arch.root_mmu.translate_gpa = translate_gpa;for (i = 0; i < KVM_MMU_NUM_PREV_ROOTS; i++)vcpu->arch.root_mmu.prev_roots[i] = KVM_MMU_ROOT_INFO_INVALID;vcpu->arch.guest_mmu.root_hpa = INVALID_PAGE;vcpu->arch.guest_mmu.root_pgd = 0;vcpu->arch.guest_mmu.translate_gpa = translate_gpa;for (i = 0; i < KVM_MMU_NUM_PREV_ROOTS; i++)vcpu->arch.guest_mmu.prev_roots[i] = KVM_MMU_ROOT_INFO_INVALID;vcpu->arch.nested_mmu.translate_gpa = translate_nested_gpa;ret = alloc_mmu_pages(vcpu, &vcpu->arch.guest_mmu);if (ret)return ret;ret = alloc_mmu_pages(vcpu, &vcpu->arch.root_mmu);if (ret)goto fail_allocate_root;return ret;fail_allocate_root:free_mmu_pages(&vcpu->arch.guest_mmu);return ret;
}
EPT表的创建

EPT的缺页处理是由函数tdp_page_fault完成的。当虚拟机内部进行内存访问的时候,MMU首先会根据虚拟机操作系统的页表把GVA转换成GPA,然后根据EPT页表把GPA转换成HPA,这就是所谓的两级页表转换,也就是tdp(two dimission page)的来源。GVA转换为GPA的过程中,如果发生缺页异常,这个异常会由虚拟机操作系统内核处理;GPA转换成HPA的过程中,如果发生缺页异常,虚拟机会产生退出,并且退出原因为EXIT_REASON_EPT_VIOLATION,其对应的处理函数为handle_ept_violation,这个函数就会调用tdp_page_fault来完成缺页异常的处理。

vcpu_enter_guest函数会完成虚拟机的进入和退出,函数最后调用架构相关的handle_exit回调函数,Intel CPU对应的是vmx_handle_exit,该函数会根据退出原因调用kvm_vmx_exit_handlers函数表中的一个函数,EPT异常会调用handle_ept_violation。

static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {...[EXIT_REASON_GDTR_IDTR]              = handle_desc,[EXIT_REASON_LDTR_TR]              = handle_desc,[EXIT_REASON_EPT_VIOLATION]          = handle_ept_violation,[EXIT_REASON_EPT_MISCONFIG]           = handle_ept_misconfig,...
};
static int handle_ept_violation(struct kvm_vcpu *vcpu)
{unsigned long exit_qualification;gpa_t gpa;u64 error_code;exit_qualification = vmx_get_exit_qual(vcpu);if (!(to_vmx(vcpu)->idt_vectoring_info & VECTORING_INFO_VALID_MASK) &&enable_vnmi &&(exit_qualification & INTR_INFO_UNBLOCK_NMI))vmcs_set_bits(GUEST_INTERRUPTIBILITY_INFO, GUEST_INTR_STATE_NMI);gpa = vmcs_read64(GUEST_PHYSICAL_ADDRESS);trace_kvm_page_fault(gpa, exit_qualification);...[省略]vcpu->arch.exit_qualification = exit_qualification;if (unlikely(allow_smaller_maxphyaddr && kvm_mmu_is_illegal_gpa(vcpu, gpa)))return kvm_emulate_instruction(vcpu, 0);return kvm_mmu_page_fault(vcpu, gpa, error_code, NULL, 0);
}

MMIO机制

在x86下访问设备的资源有两种方式:一种是通过Port I/O,即PIO;另一种是通过MemoryMapped I/O,即MMIO。虚拟机的VMCS中定义了两个IO bitmap,共2页,总共有4096×8×2=65536个位,每一个位如果进行了置位则表示虚拟机对该端口的读写操作会退出到KVM,KVM可以自己处理这些PIO的请求,但是更多时候KVM会将对PIO的请求分派到QEMU,这就是PIO的实现机制。那么MMIO应该怎么实现呢?答案是EPT。MMIO的机制简单介绍如下。

1)QEMU申明一段内存作为MMIO内存,这不会导致实际QEMU进程的内存分配。

2)SeaBIOS会分配好所有设备MMIO对应的基址。

3)当Guest第一次访问MMIO的地址时候,会发生EPT violation,产生VM Exit。

4)KVM创建一个EPT页表,并设置页表项特殊标志。

5)虚拟机之后再访问对应的MMIO地址的时候就会产生EPT misconfig,从而产生VM Exit,退出到KVM,然后KVM负责将该事件分发到QEMU。

虚拟机脏页跟踪

开启EPT并建立EPT的页表后,虚拟机中对内存的访问都是通过两级页表完成的,这是在硬件中自动完成的。但是有的时候需要知道虚拟的物理内存中有哪些内存被改变了,也就是记录虚拟机写过的内存,写过的内存叫作内存脏页,记录写过的脏页情况叫作内存脏页跟踪,脏页跟踪是热迁移的基础。热迁移能够将虚拟机从一台宿主机(源端)迁移到另一台宿主机(目的端)上,并且对客户机的影响极小,迁移过程主要就是将虚拟机的内存页迁移到目的端,在热迁移进行内存迁移的同时,虚拟机会不停地写内存,如果虚拟机在QEMU迁移了该页之后又对该页写入了新数据,那么QEMU就需要重新迁移该页,所以QEMU需要跟踪虚拟机的脏页情况。

脏页跟踪的实现

应用层软件QEMU在需要进行脏页跟踪时,会设置memslot的flags为KVM_MEM_LOG_DIRTY_PAGES,在__kvm_set_memory_region函数中,当检测到这个标识设置的时候,会调用kvm_create_dirty_bitmap创建一个脏页位图。

int __kvm_set_memory_region(struct kvm *kvm,const struct kvm_userspace_memory_region *mem)
{.../* Allocate/free page dirty bitmap as needed */if (!(new.flags & KVM_MEM_LOG_DIRTY_PAGES))new.dirty_bitmap = NULL;else if (!new.dirty_bitmap) {r = kvm_alloc_dirty_bitmap(&new);if (r)return r;...
}
EXPORT_SYMBOL_GPL(__kvm_set_memory_region);
static int kvm_alloc_dirty_bitmap(struct kvm_memory_slot *memslot)
{unsigned long dirty_bytes = 2 * kvm_dirty_bitmap_bytes(memslot);memslot->dirty_bitmap = kvzalloc(dirty_bytes, GFP_KERNEL_ACCOUNT);if (!memslot->dirty_bitmap)return -ENOMEM;return 0;
}

kvm_alloc_dirty_bitmap()分配的空间是实际的2倍。

当应用层需要知道虚拟机的内存访问情况时,调用虚拟机所属ioctl(KVM_GET_DIRTY_LOG)可以得到脏页位图。KVM中对这个ioctl的处理函数是kvm_vm_ioctl_get_dirty_log,该函数调用kvm_get_dirty_log_protect来完成实际工作。QEMU每次调用ioctl(KVM_GET_DIRTY_LOG)都能够获得虚拟机上一次进行该调用之后到现在之间的脏页情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/708068.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity绘制六边形体

现在steam上面有很多下棋类/经营类的游戏都是用六边形的地形&#xff0c;比较美观而且实用&#xff0c;去年在版本末期我也自己尝试做了一个绘制六边体的demo&#xff0c;一年没接触unity竟然都要忘光了&#xff0c;赶紧在这边记录一下。 想cv代码可以直接拉到代码章节 功能 …

音频转换器哪个好?3款电脑软件+3款手机应用

在当今的数字时代&#xff0c;音频转换已成为许多用户日常的需求。为了帮助您找到最佳的音频转换工具&#xff0c;我们将介绍3款电脑软件和3款手机应用。这些工具都各有特点&#xff0c;能够满足不同用户的需求。 1.电脑软件篇 1.1金舟音频大师 金舟音频大师是一款多功能的音…

【LabVIEW 】串口如何读取长度不一致的字符串

工程经验 1、在循环中&#xff0c;加入定时器&#xff0c;这样可以一段时间读取一次。 2、只要获取完整的一帧数据&#xff0c;就可以进行过滤筛选。

力扣128. 最长连续序列(哈希表)

Problem: 128. 最长连续序列 文章目录 题目描述思路复杂度Code 题目描述 思路 1.先将数组中的元素存入到一个set集合中&#xff08;去除重复的元素&#xff09; 2.欲找出最长连续序列&#xff08;先定义两个int变量longestSequence和currentSequence用于记录最长连续序列和当前…

Adobe Acrobat DC中如何合并pdf并生成目录

一、利用 Acrobat 合成pdf目录 &#xff08;一&#xff09;新建标签&#xff08;更改标签等级等&#xff09; 1&#xff0c;用Adobe acrobat 软件打开待添加书签的pdf文档。 2&#xff0c;打开之后点击软件左边栏的书签&#xff08;有时被隐藏了&#xff0c;点击一下界面左边…

第一节 数据操作+数据处理

本系列文章为李沐老师《动手学深度学习》Pytorch版实践学习笔记&#xff0c;相关课程教学、书籍、代码均为开源&#xff0c;可通过以下链接参考学习&#xff1a; 跟李沐学AI的个人空间-跟李沐学AI个人主页-哔哩哔哩视频 (bilibili.com) 前言 — 动手学深度学习 2.0.0 documenta…

高校物品捐赠管理系统|基于springboot高校物品捐赠管理系统设计与实现(源码+数据库+文档)

高校物品捐赠管理系统目录 目录 基于springboot高校物品捐赠管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、捐赠信息管理 3、论坛信息管理 4、公告信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算…

RabbitMQ讲解与整合

RabbitMq安装 类型概念 租户 RabbitMQ 中有一个概念叫做多租户&#xff0c;每一个 RabbitMQ 服务器都能创建出许多虚拟的消息服务器&#xff0c;这些虚拟的消息服务器就是我们所说的虚拟主机&#xff08;virtual host&#xff09;&#xff0c;一般简称为 vhost。 每一个 vhos…

NLP-词向量、Word2vec

Word2vec Skip-gram算法的核心部分 我们做什么来计算一个词在中心词的上下文中出现的概率&#xff1f; 似然函数 词已知&#xff0c;它的上下文单词的概率 相乘。 然后所有中心词的这个相乘数 再全部相乘&#xff0c;希望得到最大。 目标函数&#xff08;代价函数&#xff0…

如何用CDH+Apache DolphinScheduler开启Kerberos

搭建环境 多台linux主机搭建集群CDH 6.3.2 (Parcel)版本Apache DolphinScheduler1.3.2版本&#xff0c;本流程在CDH已搭建完成并可正常使用后&#xff0c;开启kerberos功能&#xff0c;Apache DolphinScheduler用于大数据任务管理与执行&#xff0c;是很不错的任务调度平台&am…

ZYNQ--MIG核配置

文章目录 MIG核配置界面多通道AXI读写DDR3MIG核配置界面 Clock Period: DDR3 芯片运行时钟周期,这个参数的范围和 FPGA 的芯片类型以及具体类型的速度等级有关。本实验选择 1250ps,对应 800M,这是本次实验所采用芯片可选的最大频率。注意这个时钟是 MIG IP 核产生,并输出给…

压缩视频大小的软件有哪些?5款软件推荐

压缩视频大小的软件有哪些&#xff1f;随着高清摄像设备的普及和网络速度的不断提升&#xff0c;视频文件变得越来越庞大&#xff0c;动辄数百兆甚至数GB的大小常常让用户在分享和存储时感到头疼。幸运的是&#xff0c;市面上有许多优秀的视频压缩软件可以帮助我们轻松应对这一…

NFS服务器挂载失败问题

问题 mount.nfs: requested NFS version or transport protocol is not supported背景&#xff1a;现在做嵌入式开发&#xff0c;需要在板端挂载服务器&#xff0c;读取服务器文件。挂载中遇到该问题。 挂载命令长这样 mount -t nfs -o nolock (XXX.IP):/mnt/disk1/zixi01.ch…

vue实现水印功能

目录 一、应用场景 二、实现原理 三、详细开发 1.水印的实现方式 2.防止用户通过控制台修改样式去除水印效果&#xff08;可跳过&#xff0c;有弊端&#xff09; 3.水印的使用 &#xff08;1&#xff09;单页面/全局使用 &#xff08;2&#xff09;全局使用个别页面去掉…

绘制窗口及窗口位置变化

为了方便窗口的移动 &#xff0c;及相交窗口关闭之后被遮挡窗口的重绘&#xff0c;因此给每个窗口建立一个内存BUF&#xff0c;等到不涉及内容变更的重绘&#xff0c;只需要将该BUF复制到显存之中。 然而&#xff0c;重绘时存在一个被遮挡时如何操作的问题。比如下图中依次为从…

【QT+JS】QT和JS 中的正则表达式 、QT跑JS语言

【QTJS】QT和JS 中的正则表达式 、QT跑JS语言 前言正则表达式QT 中的使用QRegExp自带的cap方法怎么用&#xff1f;QRegExp的非贪婪模式与贪婪模式 JS 中的使用 QT 跑JS 语言 前言 在看大佬的系统代码时候&#xff0c;对其中灵活用到的正则表达式和QT 跑JS 语言部分感觉很陌生&…

iOS App冷启动优化:二进制重排

原理 二进制文件中方法的加载顺序&#xff0c; 取决于方法在代码文件中的书写顺序&#xff0c;而不是调用顺序。 应用程序启动时会调用到的方法是有限的&#xff0c;但可能分散在很多个。 由于内存是分页管理的&#xff0c;要加载就要 整页加载。 这就导致很多完全还用不到的方…

网站添加pwa操作和配置manifest.json后,没有效果排查问题

pwa技术官网&#xff1a;https://web.dev/learn/pwa 应用清单manifest.json文件字段说明&#xff1a;https://web.dev/articles/add-manifest?hlzh-cn Web App Manifest&#xff1a;Web App Manifest | MDN 当网站添加了manifest.json文件后&#xff0c;也引入到html中了&a…

FPGA-FIF0模型与应用场景(IP核)

什么是FIFO FIFO (First In First Out) ,也就是先进先出。FPGA或者ASIC中使用到的FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互。它与普通存储器的区别是没有外部读写地址线,这样使用起来相对简单,但缺点就是只能顺序写…

python脚本实现全景站点欧拉角转矩阵

效果 脚本 import numpy as np import math import csv import os from settings import *def euler_to_rotation_matrix(roll, pitch, yaw):# 计算旋转矩阵# Z-Y-X转换顺序Rz