原文信息

作者:LoyenWang

出处:https://www.cnblogs.com/LoyenWang/

公众号:LoyenWang

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  1. KVM版本:5.9.1
  2. QEMU版本:5.0.0
  3. 工具:Source Insight 3.5, Visio
  4. 文章同步在博客园:https://www.cnblogs.com/LoyenWang/

1. 概述

《Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化》文中描述过内存虚拟化大体框架,再来回顾一下:

  1. 非虚拟化下的内存的访问

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

  • 虚拟化情况下,内存的访问会分为两个StageHypervisor通过Stage 2来控制虚拟机的内存视图,控制虚拟机是否可以访问某块物理内存,进而达到隔离的目的;
  • Stage 1VA(Virtual Address)->IPA(Intermediate Physical Address),Host的操作系统控制Stage 1的转换;
  • Stage 2IPA(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中的虚拟地址到物理地址的映射,就是典型的常规操作,参考之前的内存管理模块系列文章;

铺垫了这么久,来到了本文的两个主题:

  1. GPA->HVA;
  2. 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_CREATEKVM_MR_MOVEKVM_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用于更新整个memslotsmemslots基于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_LOWESR_ELx_EC_IABT_LOW两个异常,这不就是指令异常和数据异常吗,我们大胆的猜测,HVA->HPA映射的建立就在kvm_handle_guest_abort函数中。

3.1 kvm_handle_guest_abort

先来补充点知识点,可以更方便的理解接下里的内容:

  1. Guest OS在执行到敏感指令时,产生EL2异常,CPU切换模式并跳转到EL2el1_syncarch/arm64/kvm/hyp/entry-hyp.S)异常入口;
  2. CPU的ESR_EL2寄存器记录了异常产生的原因;
  3. Guest退出到kvm后,kvm根据异常产生的原因进行对应的处理。

简要看一下ESR_EL2寄存器:

  • EC:Exception class,异常类,用于标识异常的原因;
  • ISS:Instruction Specific Syndrome,ISS域定义了更详细的异常细节;
  • kvm_handle_guest_abort函数中,多处需要对异常进行判断处理;

kvm_handle_guest_abort函数,处理地址访问异常,可以分为两类:

  1. 常规内存访问异常,包括未建立页表映射、读写权限等;
  2. 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_EL2EC,并且判断是否为ESR_ELx_IABT_LOW,也就是指令异常类型;
  • kvm_vcpu_dabt_isextabt用于判断是否为同步外部异常,同步外部异常的情况下,如果支持RAS,Host能处理该异常,不需要将异常注入给Guest;
  • 异常如果不是FSC_FAULTFSC_PERMFSC_ACCESS三种类型的话,直接返回错误;
  • gfn_to_memslotgfn_to_hva_memslot_prot这两个函数,是根据IPA去获取到对应的memslot和HVA地址,这个地方就对应到了上文中第二章节中地址关系的建立了,由于建立了连接关系,便可以通过IPA去找到对应的HVA;
  • 如果注册了RAM,能获取到正确的HVA,如果是IO内存访问,那么HVA将会被设置成KVM_HVA_ERR_BADkvm_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分析(五)之内存虚拟化的更多相关文章

  1. [原] KVM 虚拟化原理探究(4)— 内存虚拟化

    KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...

  2. [转载]Linux 线程实现机制分析

    本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...

  3. KVM 介绍(2):CPU 和内存虚拟化

    学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...

  4. KVM 内存虚拟化

    内存虚拟化的概念     除了 CPU 虚拟化,另一个关键是内存虚拟化,通过内存虚拟化共享物理系统内存,动态分配给虚拟机.虚拟机的内存虚拟化很象现在的操作系统支持的虚拟内存方式,应用程序看到邻近的内存 ...

  5. KVM(二)CPU 和内存虚拟化

    1. 为什么需要 CPU 虚拟化 X86 操作系统是设计在直接运行在裸硬件设备上的,因此它们自动认为它们完全占有计算机硬件.x86 架构提供四个特权级别给操作系统和应用程序来访问硬件. Ring 是指 ...

  6. 2017.4.28 KVM 内存虚拟化及其实现

    概述 KVM(Kernel Virtual Machine) , 作为开源的内核虚拟机,越来越受到 IBM,Redhat,HP,Intel 等各大公司的大力支持,基于 KVM 的开源虚拟化生态系统也日 ...

  7. 【原创】Linux虚拟化KVM-Qemu分析(五)之内存虚拟化

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  8. 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设备直接分 ...

  9. KVM+QEMU虚拟化概念

    概念: KVM,即Kernel-basedvirtual machine,由redhat开发,是一种开源.免费的虚拟化技术.对企业来说,是一种可选的虚拟化解决方案. 定义:基于Linux内核的虚拟机 ...

  10. KVM,QEMU核心分析

    现在的问题是学习虚拟化软件KVM相关实施原则.处理,的源代码的分析总结,,若有不对的地方,希望大家提出. 因为有一些代码结构图或者是架构图上传比較麻烦.所以博文都放在了自己的个人博客上.麻烦大家移步查 ...

随机推荐

  1. Rocky Linux 初体验

    简介 Rocky Linux 是 CentOS 的一个分支,它位于 Red Hat Enterprise Linux(RHEL) 的下游.与 CentOS 一样,它提供了非常适合服务器的稳定版 Lin ...

  2. 迁移学习(MEnsA)《MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point Clouds》

    论文信息 论文标题:MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point ...

  3. 视频会议中的AEC、AGC、ANS是什么?

    视频会议中的AEC.AGC.ANS是什么? 1.AGC是自动增益补偿功能(Automatic Gain Control),AGC可以自动调麦克风的收音量,使与会者收到一定的音量水平,不会因发言者与麦克 ...

  4. JS Bom(window)对象

    window 是客户端浏览器对象模型的基类,window 对象是客户端 JavaScript 的全局对象.一个 window 对象实际上就是一个独立的窗口,对于框架页面来说,浏览器窗口每个框架都包含一 ...

  5. vue下载文件模板(excel) 和 导出excel表格

    1. get形式传参数 仅限于get方式,注意请求头参数...,需要后台放开 window.location = '/dms-underlying-asset/download?assetType=' ...

  6. 安装dataX的问题,com.alibaba.datax.common.exception.DataXException: Code:[Common-00], Describe

    文章目录 报错 安装: 解决方法 总结 报错 com.alibaba.datax.common.exception.DataXException: Code:[Common-00], Describe ...

  7. 使用ChatGPT4协助完成读取文件中不同字的数量

    使用ChatGPT4识别:用java读取文件中不同字的个数. 解析:该程序将读取名为"file.txt"的文件,并计算文件中每个不同字的出现次数.它使用一些字符串操作来清理单词,并 ...

  8. ai问答:使用 Vue3 组合式API 和 TS 配置 axios 拦截器 http错误状态

    通过 axios.create() 可以创建一个 axios 实例 axiosInstance,参数如下: baseURL:请求前缀 timeout:超时时间 headers:请求头 默认配置: im ...

  9. Vue3项目的打包运行

    一.项目打包(vite创建的项目) 执行以下这条命令对项目进行打包 npm run build 生成dist文件夹,进入dist文件夹下的index.html文件,然后右键选择Open with Li ...

  10. 2020-11-04:java里,总体说一下集合框架。

    福哥答案2020-11-04: 福哥口诀法:收马李色坤(Collection.Map.List.Set.Queue).李矢数链写(List:Vector矢量.ArrayList数组.LinkedLis ...