QEMU源码全解析 —— 内存虚拟化(1)

接前一篇文章:

本文内容参考:

《趣谈Linux操作系统》 —— 刘超,极客时间

《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社

内存虚拟化硬件基础——EPT

特此致谢!

内存虚拟化简介

内存是计算机必不可少的组成部分之一,因此内存的虚拟化也是各类虚拟化方案必须要解决的问题。从CPU的视角来看,物理机上的内存是一段从0开始的连续可用的物理内存。在虚拟化中,每个虚拟机都需要这么一段从0开始的、连续的、属于自己的物理地址。为此,VMM必须也为虚拟机模拟出这样一段空间。首先且比较容易想到的就是QEMU进程的虚拟地址作为这样一段空间提供给物理内存,如下图所示:

QEMU源码全解析 —— 内存虚拟化(1)_第1张图片

但是这样的方案会存在问题:在物理机上,CPU对内存的访问在保护模式下是通过分段分页实现的,也就是说在保护模式下,CPU访问时使用的是虚拟地址而非物理地址。虚拟地址必须通过硬件MMU进行转换,将其转换为物理地址,之后才能访问到实际的物理内存。如下图所示:

QEMU源码全解析 —— 内存虚拟化(1)_第2张图片

但是在虚拟化下,虚拟机内部也有自己的保护模式。因此,当虚拟机中的虚拟CPU进行内存寻址的时候,其使用的是虚拟机内部的虚拟地址。要想让其访问到实际的物理内存,必须先将这个虚拟机的虚拟地址转换成虚拟机的物理地址然后将虚拟机的物理地址转换成QEMU的虚拟地址(即物理机上的虚拟地址);最后将QEMU的虚拟地址转换成物理机上的物理地址,才能访问到数据。

这个过程中涉及到四类内存:

  • 虚拟机中的虚拟内存地址(Guest OS Virtual Memory Address,GVA)

这是虚拟机里面的进程看到的内存空间。

  • 虚拟机中的物理内存地址(Guest OS Physical Memory Address,GPA)

这是虚拟机里面的操作系统看到的内存,它认为这是物理内存。

  • 物理机的虚拟内存地址(Host Virtual Memory Address,HVA)

这是物理机上的QEMU进程看到的内存空间。

  • 物理机的物理内存地址(Host Physical Memory Address,HPA)

这是物理机上的操作系统看到的内存。

也即,上边虚拟机中的进程访问物理内存时,需要经过GVA->GPA->HVA->HPA的过程。咱们作为旁观者,在一旁看着这一过程都感觉费劲,就甭说实际的效果了。经过这个特别“绕”的过程,计算机的性能算彻底“日本船”了。显然,这需要虚拟化软件提供一种机制来完成这种转换(而且还不能单纯是以上那个过程),这就是所谓MMU的虚拟化。

影子页表与EPT

  • 影子页表

QEMU源码全解析 —— 内存虚拟化(1)_第3张图片

在CPU没有支持EPT(Extended Page Table,扩展页表)之前,虚拟机是通过影子页表实现从虚拟机虚拟地址到宿主机物理地址的转换的,这是一种软件实现方案。在该方案中,影子页表直接实现GVA到HPA的转换,绕过了中间的GPA和HVA。这一没有中间商,果然开销就少了不少。虚拟化软件(KVM)为虚拟机中的每一个进程保存一个页表,虚拟机中的进程也有自己的页表,但是这个页表是可读的,所以就称前一个页表是后一个页表的影子页表。这样虚拟机的页表就会导致VM Exit(即由非根模式切换成根模式),然后KVM会处理该请求,然后更新影子页表。

影子页表的基本原理是:

1)当处于非根模式下的CPU有修改CR3的动作时(通常是下一个进程被调度的时候),意味着有页目录地址会被加载到CR3(如果是32-bit分页模式),这时VMM截获这个动作,退回到根模式;

2)VMM中维护了一个hash链表,专用于存放CR3中页目录地址对应的影子地址。VMM拿到页目录地址后计算该地址的hash值并作为key放到hash表中,然后申请一个主机上的物理地址作为页目录的地址放到CR3寄存器中,同时主机上的物理地址也被存放到hash表中,CR3原有的应该被保存的页目录地址被VMM保存起来;

3)当虚拟机进程被调度出去的时候,CR3中应该被保存的页目录地址要放回到进程控制块中,这时VMM会截获这个动作并提供之前保存的页目录地址。

QEMU源码全解析 —— 内存虚拟化(1)_第4张图片

VMM在CR3寄存器被修改的过程中,偷天换日,将本应该放到CR3中的地址拿走,替换成自己申请的地址,当客户机要从CR3中取回这个地址时替换回去。

总之,影子页表的方案使用纯软件实现了一个MMU。

  • EPT(Extended Page Table,扩展页表)

由于影子页表使用纯软件实现MMU的方案效率很低,因此VMX架构引入了所谓的扩展页表,即EPT方案。

在EPT方案中,CPU的寻址模式在VM non-root operation下会发生变化,其会使用两个页表。其基本原理如下图所示:

QEMU源码全解析 —— 内存虚拟化(1)_第5张图片

如果开启了EPT,当CPU进行VM Entry时,会使用EPT功能。虚拟机对内部自身页表有着完全的控制,CPU先将虚拟机内部的虚拟地址转换为虚拟机物理地址,通过此过程查找虚拟机内部页表;然后CPU会将这个地址转换为宿主机的物理地址,通过此过程查找宿主机中的EPT页表。当CPU产生VM Exit时,EPT会关闭,这个时候CPU在宿主机上就又会按照传统的单页方式寻址。

虚拟机中的页表就像物理机操作系统页表一样,页表里边保存的是虚拟机物理地址,由自己维护;EPT页表则由主机维护,里边记录着虚拟机物理地址到宿主机物理地址的转换。

EPT克服了影子页表使用软件维护GVA->HPA地址转换的缺点,它使用硬件来维护GPA->HPA,因此效率大大提高。而且,影子页表虽然缩短了地址转换路径,但每次虚机进程访问CR3时,都会引起VMX的模式切换,开销很大。影子页表在每次加载和卸载的时候都会引起模式切换,而EPT减少了这种开销,EPT只在缺页的时候才引起VMX模式切换,一旦页表建立好之后,EPT就不再有模式切换的开销,虚机内存的访问一直在客户态。

一般来讲,EPT使用的是IA-32e的分页模式,也就是使用48位物理地址,总共分为四级页表,每级页表使用9位物理地址,最后12位表示在一个页(4KB)内的偏移,如下图所示:

QEMU源码全解析 —— 内存虚拟化(1)_第6张图片

在EPT分页基址下,虚拟机进行内存访问会引发一系列的GPA到HPA的转换。上图展示了EPT的访问过程,EPT页表的基址保存在VMCS(Virtual Machine Control Structure)结构内的EPTP(extended-page-table pointer)指针中。在进行一次内存访问时,虚拟机首先将客户机虚拟内存转换为客户机物理内存地址,这中间会查询虚拟机的页表。如果对应页表存在且有效,则读取此页表信息,然后读取下一级页表,计算出虚拟机需要访问的GPA,然后这个GPA会再一次执行页表查找,最终找到物理机上的数据。在以上过程中,如果虚拟机内部发生缺页异常,则虚拟机会自己修好自己的页表;如果发生EPT异常,则产生EPT异常退出,虚拟机会退出到KVM,构建好对应的EPT页表。

欲知后事如何,且看下回分解。

你可能感兴趣的:(QEMU,KVM,QEMU,KVM)