【转载】Linux虚拟化KVM-Qemu分析(五)之内存虚拟化
作者:LoyenWang
出处:https://www.cnblogs.com/LoyenWang/
公众号:LoyenWang
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
背景
- Read the fucking source code!--By 鲁迅
- A picture is worth a thousand words.--By 高尔基
说明:
- KVM版本:5.9.1
- QEMU版本:5.0.0
- 工具:Source Insight 3.5, Visio
- 文章同步在博客园:https://www.cnblogs.com/LoyenWang/
1. 概述
《Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化》文中描述过内存虚拟化大体框架,再来回顾一下:
- 非虚拟化下的内存的访问

- CPU访问物理内存前,需要先建立页表映射(虚拟地址到物理地址的映射),最终通过查表的方式来完成访问。在ARMv8中,内核页表基地址存放在TTBR1_EL1中,用户空间页表基地址存放在TTBR0_EL0中;
- 虚拟化下的内存访问

- 虚拟化情况下,内存的访问会分为两个Stage,Hypervisor通过Stage 2来控制虚拟机的内存视图,控制虚拟机是否可以访问某块物理内存,进而达到隔离的目的;
- Stage 1:- VA(Virtual Address)->IPA(Intermediate Physical Address),Host的操作系统控制- Stage 1的转换;
- Stage 2:- IPA(Intermediate Physical Address)->PA(Physical Address),Hypervisor控制- Stage 2的转换;
猛一看上边两个图,好像明白了啥,仔细一想,啥也不明白,本文的目标就是将这个过程讲明白。
在开始细节讲解之前,需要先描述几个概念:
gva - guest virtual address
gpa - guest physical address
hva - host virtual address
hpa - host physical address

- Guest OS中的虚拟地址到物理地址的映射,就是典型的常规操作,参考之前的内存管理模块系列文章;
铺垫了这么久,来到了本文的两个主题:
- GPA->HVA;
- HVA->HPA;
开始吧!
2. GPA->HVA
还记得上一篇文章《Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)》中的Sample Code吗?
KVM-Qemu方案中,GPA->HVA的转换,是通过ioctl中的KVM_SET_USER_MEMORY_REGION命令来实现的,如下图:

找到了入口,让我们进一步揭开神秘的面纱。
2.1 数据结构
关键的数据结构如下:

- 虚拟机使用slot来组织物理内存,每个slot对应一个struct kvm_memory_slot,一个虚拟机的所有slot构成了它的物理地址空间;
- 用户态使用struct kvm_userspace_memory_region来设置内存slot,在内核中使用struct kvm_memslots结构来将kvm_memory_slot组织起来;
- struct kvm_userspace_memory_region结构体中,包含了- slot的ID号用于查找对应的- slot,此外还包含了物理内存起始地址及大小,以及HVA地址,HVA地址是在用户进程地址空间中分配的,也就是Qemu进程地址空间中的一段区域;
2.2 流程分析
数据结构部分已经罗列了大体的关系,那么在KVM_SET_USER_MEMORY_REGION时,围绕的操作就是slots的创建、删除,更新等操作,话不多说,来图了:

- 当用户要设置内存区域时,最终会调用到__kvm_set_memory_region函数,在该函数中完成所有的逻辑处理;
- __kvm_set_memory_region函数,首先会对传入的- struct kvm_userspace_memory_region的各个字段进行合法性检测判断,主要是包括了地址的对齐,范围的检测等;
- 根据用户传递的slot索引号,去查找虚拟机中对应的slot,查找的结果只有两种:1)找到一个现有的slot;2)找不到则新建一个slot;
- 如果传入的参数中memory_size为0,那么会将对应slot进行删除操作;
- 根据用户传入的参数,设置slot的处理方式:KVM_MR_CREATE,KVM_MR_MOVE,KVM_MEM_READONLY;
- 根据用户传递的参数决定是否需要分配脏页的bitmap,标识页是否可用;
- 最终调用kvm_set_memslot来设置和更新slot信息;
2.2.1 kvm_set_memslot
具体的memslot的设置在kvm_set_memslot函数中完成,slot的操作流程如下:

- 首先分配一个新的memslots,并将原来的memslots内容复制到新的memslots中;
- 如果针对slot的操作是删除或者移动,首先根据旧的slot id号从memslots中找到原来的slot,将该slot设置成不可用状态,再将memslots安装回去。这个安装的意思,就是RCU的assignment操作,不理解这个的,建议去看看之前的RCU系列文章。由于slot不可用了,需要解除stage2的映射;
- kvm_arch_prepare_memory_region函数,用于处理新的- slot可能跨越多个用户进程VMA区域的问题,如果为设备区域,还需要将该区域映射到- Guest IPA中;
- update_memslots用于更新整个- memslots,- memslots基于PFN来进行排序的,添加、删除、移动等操作都是基于这个条件。由于都是有序的,因此可以选择二分法来进行查找操作;
- 将添加新的slot后的memslots安装回KVM中;
- kvfree用于将原来的- memslots释放掉;
2.2.2 kvm_delete_memslot
kvm_delete_memslot函数,实际就是调用的kvm_set_memslot函数,只是slot的操作设置成KVM_MR_DELETE而已,不再赘述。
3. HVA->HPA
光有了GPA->HVA,似乎还是跟Hypervisor没有太大关系,到底是怎么去访问物理内存的呢?貌似也没有看到去建立页表映射啊?
跟我走吧,带着问题出发!
之前内存管理相关文章中提到过,用户态程序中分配虚拟地址vma后,实际与物理内存的映射是在page fault时进行的。那么同样的道理,我们可以顺着这个思路去查找是否HVA->HPA的映射也是在异常处理的过程中创建的?答案是显然的。
回顾一下前文《Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)》的一张图片:

- 当用户态触发kvm_arch_vcpu_ioctl_run时,会让Guest OS去跑在Hypervisor上,当Guest OS中出现异常退出到Host时,此时handle_exit将对退出的原因进行处理;
异常处理函数arm_exit_handlers如下,具体调用选择哪个处理函数,是根据ESR_EL2, Exception Syndrome Register(EL2)中的值来确定的。
static exit_handle_fn arm_exit_handlers[] = {
	[0 ... ESR_ELx_EC_MAX]	= kvm_handle_unknown_ec,
	[ESR_ELx_EC_WFx]	= kvm_handle_wfx,
	[ESR_ELx_EC_CP15_32]	= kvm_handle_cp15_32,
	[ESR_ELx_EC_CP15_64]	= kvm_handle_cp15_64,
	[ESR_ELx_EC_CP14_MR]	= kvm_handle_cp14_32,
	[ESR_ELx_EC_CP14_LS]	= kvm_handle_cp14_load_store,
	[ESR_ELx_EC_CP14_64]	= kvm_handle_cp14_64,
	[ESR_ELx_EC_HVC32]	= handle_hvc,
	[ESR_ELx_EC_SMC32]	= handle_smc,
	[ESR_ELx_EC_HVC64]	= handle_hvc,
	[ESR_ELx_EC_SMC64]	= handle_smc,
	[ESR_ELx_EC_SYS64]	= kvm_handle_sys_reg,
	[ESR_ELx_EC_SVE]	= handle_sve,
	[ESR_ELx_EC_IABT_LOW]	= kvm_handle_guest_abort,
	[ESR_ELx_EC_DABT_LOW]	= kvm_handle_guest_abort,
	[ESR_ELx_EC_SOFTSTP_LOW]= kvm_handle_guest_debug,
	[ESR_ELx_EC_WATCHPT_LOW]= kvm_handle_guest_debug,
	[ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug,
	[ESR_ELx_EC_BKPT32]	= kvm_handle_guest_debug,
	[ESR_ELx_EC_BRK64]	= kvm_handle_guest_debug,
	[ESR_ELx_EC_FP_ASIMD]	= handle_no_fpsimd,
	[ESR_ELx_EC_PAC]	= kvm_handle_ptrauth,
};
用你那双水汪汪的大眼睛扫描一下这个函数表,发现ESR_ELx_EC_DABT_LOW和ESR_ELx_EC_IABT_LOW两个异常,这不就是指令异常和数据异常吗,我们大胆的猜测,HVA->HPA映射的建立就在kvm_handle_guest_abort函数中。
3.1 kvm_handle_guest_abort
先来补充点知识点,可以更方便的理解接下里的内容:
- Guest OS在执行到敏感指令时,产生EL2异常,CPU切换模式并跳转到EL2的el1_sync(arch/arm64/kvm/hyp/entry-hyp.S)异常入口;
- CPU的ESR_EL2寄存器记录了异常产生的原因;
- Guest退出到kvm后,kvm根据异常产生的原因进行对应的处理。
简要看一下ESR_EL2寄存器:

- EC:Exception class,异常类,用于标识异常的原因;
- ISS:Instruction Specific Syndrome,ISS域定义了更详细的异常细节;
- 在kvm_handle_guest_abort函数中,多处需要对异常进行判断处理;
kvm_handle_guest_abort函数,处理地址访问异常,可以分为两类:
- 常规内存访问异常,包括未建立页表映射、读写权限等;
- IO内存访问异常,IO的模拟通常需要Qemu来进行模拟;
先看一下kvm_handle_guest_abort函数的注释吧:
/**
 * kvm_handle_guest_abort - handles all 2nd stage aborts
 *
 * Any abort that gets to the host is almost guaranteed to be caused by a
 * missing second stage translation table entry, which can mean that either the
 * guest simply needs more memory and we must allocate an appropriate page or it
 * can mean that the guest tried to access I/O memory, which is emulated by user
 * space. The distinction is based on the IPA causing the fault and whether this
 * memory region has been registered as standard RAM by user space.
 */
- 到达Host的abort都是由于缺乏Stage 2页表转换条目导致的,这个可能是Guest需要分配更多内存而必须为其分配内存页,或者也可能是Guest尝试去访问IO空间,IO操作由用户空间来模拟的。两者的区别是触发异常的IPA地址是否已经在用户空间中注册为标准的RAM;
调用流程来了:

- kvm_vcpu_trap_get_fault_type用于获取- ESR_EL2的数据异常和指令异常的- fault status code,也就是- ESR_EL2的ISS域;
- kvm_vcpu_get_fault_ipa用于获取触发异常的IPA地址;
- kvm_vcpu_trap_is_iabt用于获取异常类,也就是- ESR_EL2的- EC,并且判断是否为- ESR_ELx_IABT_LOW,也就是指令异常类型;
- kvm_vcpu_dabt_isextabt用于判断是否为同步外部异常,同步外部异常的情况下,如果支持RAS,Host能处理该异常,不需要将异常注入给Guest;
- 异常如果不是FSC_FAULT,FSC_PERM,FSC_ACCESS三种类型的话,直接返回错误;
- gfn_to_memslot,- gfn_to_hva_memslot_prot这两个函数,是根据IPA去获取到对应的memslot和HVA地址,这个地方就对应到了上文中第二章节中地址关系的建立了,由于建立了连接关系,便可以通过IPA去找到对应的HVA;
- 如果注册了RAM,能获取到正确的HVA,如果是IO内存访问,那么HVA将会被设置成KVM_HVA_ERR_BAD。kvm_is_error_hva或者(write_fault && !writable)代表两种错误:1)指令错误,向Guest注入指令异常;2)IO访问错误,IO访问又存在两种情况:2.1)Cache维护指令,则直接跳过该指令;2.2)正常的IO操作指令,调用io_mem_abort进行IO模拟操作;
- handle_access_fault用于处理访问权限问题,如果内存页无法访问,则对其权限进行更新;
- user_mem_abort,用于分配更多的内存,实际上就是完成Stage 2页表映射的建立,根据异常的IPA地址,已经对应的HVA,建立映射,细节的地方就不表了。
来龙去脉摸清楚了,那就草草收场吧,下回见了。
参考
《Arm Architecture Registers Armv8, for Armv8-A architecture profile》
欢迎关注个人公众号,不定期分享技术文章。

【转载】Linux虚拟化KVM-Qemu分析(五)之内存虚拟化的更多相关文章
- [原] KVM 虚拟化原理探究(4)— 内存虚拟化
		KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ... 
- [转载]Linux 线程实现机制分析
		本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ... 
- KVM 介绍(2):CPU 和内存虚拟化
		学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ... 
- KVM 内存虚拟化
		内存虚拟化的概念 除了 CPU 虚拟化,另一个关键是内存虚拟化,通过内存虚拟化共享物理系统内存,动态分配给虚拟机.虚拟机的内存虚拟化很象现在的操作系统支持的虚拟内存方式,应用程序看到邻近的内存 ... 
- KVM(二)CPU 和内存虚拟化
		1. 为什么需要 CPU 虚拟化 X86 操作系统是设计在直接运行在裸硬件设备上的,因此它们自动认为它们完全占有计算机硬件.x86 架构提供四个特权级别给操作系统和应用程序来访问硬件. Ring 是指 ... 
- 2017.4.28  KVM 内存虚拟化及其实现
		概述 KVM(Kernel Virtual Machine) , 作为开源的内核虚拟机,越来越受到 IBM,Redhat,HP,Intel 等各大公司的大力支持,基于 KVM 的开源虚拟化生态系统也日 ... 
- 【原创】Linux虚拟化KVM-Qemu分析(五)之内存虚拟化
		背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ... 
- KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]
		学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ... 
- KVM+QEMU虚拟化概念
		概念: KVM,即Kernel-basedvirtual machine,由redhat开发,是一种开源.免费的虚拟化技术.对企业来说,是一种可选的虚拟化解决方案. 定义:基于Linux内核的虚拟机 ... 
- KVM,QEMU核心分析
		现在的问题是学习虚拟化软件KVM相关实施原则.处理,的源代码的分析总结,,若有不对的地方,希望大家提出. 因为有一些代码结构图或者是架构图上传比較麻烦.所以博文都放在了自己的个人博客上.麻烦大家移步查 ... 
随机推荐
- Linux(二)文件权限和压缩
			1 搜索查找类 1.1 查找定位文件 find <搜索范围.路径> <选项> find将从指定目录下递归地遍历其各个子目录,将满足条件的文件显示在终端. 选项说明 -name: ... 
- CSS实现单行或者多行文本溢出隐藏并且显示省略号
			一.单行超出显示省略号如果文字超出父元素指定宽度,文字会自动换行,而连续不间断数字和英文字母(没有其他字符)不会自动换行: 详细步骤: 第一步(不换行):white-space:nowrop;(对于连 ... 
- Android事件分发-基础原理和场景分析
			作者:京东零售 郭旭锋 1 为什么需要事件分发 和其他平台类似,Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的 ... 
- Windows防病毒Defender 排除病毒误报
			开发的软件安装后,windows上提示病毒,默默被系统删除了. 一开始以为是自己软件的签名问题,后面发现,将被隔离的文件还原,文件的签名是存在的. 这是微软denfender的误报,为啥会报病毒呢? ... 
- [OpenCV-Python] 16 图像平滑
			文章目录 OpenCV-Python:IV OpenCV中的图像处理 16 图像平滑 16.1 平均 16.2 高斯模糊 16.3 中值模糊 16.4 双边滤波 OpenCV-Python:IV Op ... 
- Django笔记三十六之单元测试汇总介绍
			本文首发于公众号:Hunter后端 原文链接:Django笔记三十六之单元测试汇总介绍 Django 的单元测试使用了 Python 的标准库:unittest. 在我们创建的每一个 applicat ... 
- 【开源游戏】Legends-Of-Heroes  基于ET 7.2的双端C#(.net7 + Unity3d)多人在线英雄联盟风格的球球大作战游戏。
			Legends-Of-Heroes 一个LOL风格的球球大作战游戏,基于ET7.2,使用状态同步 Main 基于C#双端框架[ET7.2],同步到ET主干详情请看日志.(https://github ... 
- k8s资源对象
			什么是资源对象? 所谓资源对象是指在k8s上创建的资源实例:即通过apiserver提供的各资源api接口(可以理解为各种资源模板),使用yaml文件或者命令行的方式向对应资源api接口传递参数赋值实 ... 
- mac 如何快捷键打开当前文件夹对应的终端窗口
- sql server 删除带依赖的列 由于一个或多个对象访问此 列
			--SELECT * FROM LJEL005H--ALTER TABLE LJEL005H add el_req int default 15 not null --消息 5074,级别 16,状态 ... 
