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

LoyenWang 2020-11-07 23:59:06
linux 原创 KVM 虚拟 kvm-qemu


背景

  • 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》

欢迎关注个人公众号,不定期分享技术文章。

版权声明
本文为[LoyenWang]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/LoyenWang/p/13943005.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云