calinyara

calinyara 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织 calinyara.github.io/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

calinyara 发布了文章 · 2020-06-13

基于树莓派搭建小型云计算集群

拥有一个私人的云计算平台是一件很酷的事情。随着技术的发展,实现这一愿望已经变得相当容易。接下来就来说明如何利用树莓派硬件和相关软件搭建一个用于边缘计算的小型云计算集群。

1 硬件准备

硬件优先考虑树莓派。选择ARM而不是x86架构硬件,主要是考虑到该云计算平台主要用于私人,家庭以及边缘计算等应用场景。一方面,ARM硬件相对便宜,功耗低,性价比更高;另一方面树莓派拥有成熟的社区生态,可用的软件也比较丰富。

选择1. 树莓派3B及其之前的版本

树莓派3B及其之前的版本由于不支持以太网口供电(PoE), 因此需要额外的USB供电插头。所有树莓派板子都连接到一个交换机/路由器,如下图所示

图1: 基于树莓派3B搭建的集群

选择2. 树莓派3B+,树莓派4B

树莓派3B+/4B拥有以太网口供电(PoE)功能, 因此可省去USB供电插头。所有树莓派板子都连接到一个支持PoE功能的交换机/路由器,如下图所示

图2: 基于树莓派3B+/4B搭建的集群

选择3. Turing Pi主板 + 树莓派计算模块

关于树莓派计算模块

上面介绍的树莓派3B, 3B+, 4B等板子其实可以拆解成如下两部分,即: 计算模块和计算模块IO扩展板.

图3: 树莓派计算模块

图4: 树莓派计算模块IO扩展板

将计算模块和计算模块IO扩展板结合起来功能就和上述的树莓派3B, 3B+, 4B 一致。

图5: 树莓派计算模块 + IO扩展板

关于Turing Pi主板

利用Turing Pi主板加可扩展树莓派计算模块的方式搭建集群十分的方便。该板最大支持7块树莓派计算模块,并可进行动态扩展,类似于数据中心的刀片服务器 (blade server)

图6: 树莓派计算模块 + Turing Pi 主板

Turing Pi同时支持带eMMC的计算模块和不带eMMC的计算模块,其第一个槽可用于烧写操作系统镜像到计算模块eMMC。对于不带eMMC的计算模块可以通过传统的插SD卡的方式启动。

2 烧写树莓派系统

2.1 系统选择

推荐如下两个系统

2.2 烧写系统

选择1. 烧写系统到SD卡

具体步骤参考如下页面

https://www.raspberrypi.org/d...

选择2. 烧写系统到树莓派计算模块的eMMC

具体步骤参考如下页面 (需要借助树莓派计算模块IO扩展板或者Turing Pi主板)

http://raspberrypiwiki.com/How_to_Burning_System_for_the_eMMC_of_Raspberry_Pi_Compute_Module

所有单板都烧写好后按照硬件准备中的描述连接好,并将每个板子配置好ssh连接,将公钥放置在~/.ssh/authorized_keys里面,以方便连接。确保可以通过***ssh@***能连上集群中每一个树莓派节点。

3 安装Kubernetes并连接集群

3.1 安装Kubernetes

Lightweight Kubernetes (K3S)是一个面向IoT及边缘计算的Kubernetes版本,比较适合树莓派等资源有限的硬件。

方式1:在每个树莓派板子上单独安装

在server节点上运行

curl -sfL https://get.k3s.io | sh -

在每个worker节点上运行

curl -sfL https://get.k3s.io | K3S_URL=https://<server_ip>:6443 K3S_TOKEN=<token> sh -

PS:K3S_TOKEN:存在server节点的 **/var/lib/rancher/k3s/server/node-token**.

方式2:使用Ansible自动化安装

先在控制机上安装Ansible

sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install ansible

下载k3s-ansible

git clone https://github.com/rancher/k3s-ansible.git

然后按照 https://github.com/rancher/k3... 的步骤,在inventory/my-cluster/hosts.ini里配置好server (master) 节点和worker(node)节点的IP地址。

运行如下命令,Ansible会将K3S自动安装在集群的server节点和每个worker节点上

ansible-playbook site.yml -i inventory/my-cluster/hosts.ini --ask-become-pass

3.2 连接集群

在控制机上安装kubectl

sudo apt-get update && sudo apt-get install -y apt-transport-https gnupg2
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl

将配置文件从server节点拷贝至控制机并配置环境变量

scp <user_name>@<server_ip>:~/.kube/config ~/.kube/rasp-config
export KUBECONFIG=~/.kube/rasp-config

连接查看集群

kubectl get nodes

大功告成,接下来就可以部署服务到集群了。

查看原文

赞 1 收藏 0 评论 0

calinyara 发布了文章 · 2020-06-01

ASOR - 基于x86架构的虚拟机实现

1. 概述

操作系统作为计算机硬件的管理软件直接运行在物理硬件之上,完全占有全部的硬件资源。自1965年以来,半导体硬件一直遵循摩尔定律快速发展。 硬件的计算能力不断增强,一套硬件上运行一个操作系统就显得有些浪费,这种现象在数据中心尤为明显。于是如何共享和整合硬件资源,提升硬件利用效率就成了一个需要解决的问题,虚拟化技术便应运而生。最近十几年,随着云计算的快速发展,虚拟化技术广泛应用于数据中心,已经成为云计算的核心技术之一。

虚拟化需要解决三个基本问题,共享隔离性能。在一套硬件环境上运行多个系统来共享硬件资源提升硬件利用效率是虚拟化的出发点,系统之间相互隔离保证安全是一种基本需求,操作系统运行在虚拟化的环境之上性能会有所下降,如何提升虚拟化的性能,一直伴随着虚拟化的发展。虚拟化经历了二进制翻译半虚拟化硬件辅助虚拟化的发展。现代CPU基本都已经支持硬件辅助虚拟化。那么,如何实现一个简单的硬件辅助虚拟化系统呢?

图1:虚拟化要解决的问题

ASOR, 是一个简单的虚拟化管理软件(VMM),用来帮助人们了解硬件辅助虚拟化的基本概念以及虚拟化管理软件的实现。虚拟化管理软件又被称作Hypervisor,有些文献将Hypervisor分成TYPE 1和TYPE 2两种类型,如图2所示。所谓TYPE 1类型也就是Hypervisor直接运行在硬件上,虚拟操作系统运行在Hypervisor之上,而TYPE 1类型的Hypervisor则运行在一个被称之为HOST的操作系统之上,其上再运行虚拟操作系统。其实,如果把TYPE 2中的HOST操作系统与hypervisor并成一个整体来看,这个整体也可以认为是一个TYPE 1的 Hypervisor。实现一个简单的TYPE 1类型的Hypervisor和实现一个简单的操作系统类似,前者比后者多了虚拟化的软件支持。从这个意义上,ASOR作为一个TYPE 1的Hypervisor也可以用来学习研究如何实现一个简单的操作系统。

图2:虚拟机类型 TYPE 1 vs TYPE 2

支持虚拟化的硬件架构

用户态,内核态,特权指令,敏感指令

x86架构提供四个特权级别给操作系统和应用程序来访问硬件,从Ring 0 到 Ring 3。 其中Ring 0为最高级别,Ring 3为最低级别。计算机指令可以分成特权指令和非特权指令。操作系统内核代码在最高级别Ring 0上运行,可以执行特权指令直接访问硬件,而应用程序代码运则行在Ring 3上,只能执行非特权指令,这些指令不能做受控操作。当一个应用程序需要访问硬件时,需要通过执行系统调用,CPU的运行级别从Ring 3 转换到 Ring 0,并跳转执行内核代码来完成硬件访问,访问完成后再从Ring 3切回Ring 0。这个过程被称作用户态和内核态切换。计算机指令还可以划分成敏感指令和非敏感指令。所谓敏感指令,指的是那些会改变系统硬件资源配置的指令或其执行的行为依赖硬件资源配置的指令。一个可以虚拟化的硬件架构需要满足的条件是,敏感指令集必须是特权指令集的子集,如图3 所示。如果存在敏感指令是非特权指令,这样的硬件就存在虚拟化空洞,是不可虚拟化的硬件架构。这种硬件虽然不能完整支持辅助虚拟化,但仍然可以通过软件方法来实现虚拟化。

图3:可虚拟化架构

早期的硬件没有辅助虚拟化的功能。虚拟化通过修改操作系统来实现,也就是对执行的非特权敏感指令进行修改,来填补虚拟化空洞来实现虚拟化,即所谓的半虚拟化。随着硬件的发展,硬件辅助虚拟化已经成为主流。

2. ASOR的启动流程

计算机上电启动后开始执行BIOS的代码,BIOS程序首先进行硬件自检。如果硬件出问题,主板会根据具体问题发出不同类型的蜂鸣声。如果没有问题,BIOS会找到启动顺序排位第一的存储设备,读取该设备的起始扇区的512个字节。如果这512个字节的最后两个字节是_0x55_和_0xAA_,表示这个设备可以用于启动,如果不是,表示设备不能用于启动,则从下一个存储设备查找。在找到可启动的设备后,控制权交给bootloader, 例如我们这里用的GRUB。通过GRUB的界面选择ASOR启动。之后程序开始执行_x86/cstart.c_(对于32位系统)或_x86/cstart64.c_(对于64位系统)。asor.img是一个虚拟硬盘,用于方便在在qemu中调试ASOR。在系统中安装qemu-kvm后,运行make qemu便可看到如 图4所示的ASOR启动界面。

图4:ASOR启动流程

以x86/cstart.c为例,其中start标记的地址是AOSR启动代码的入口地址。从start启动代码的入口地址到ASOR C语言的main函数asor_entry的流程如图5所示。

图5:从start入口到asor_entry

3. 分段与分页

x86体系结构中CPU看到的地址是逻辑地址,经过分段单元后逻辑地址被转换为线性地址,又叫虚拟地址,最后分页单元再将虚拟地址转换为物理地址,如图6。操作系统为了隔离内核空间与用户空间,通常都会定义四个段,这些段相互重叠都从地址0起始,覆盖整个线性地址空间。在特权级0上定义内核空间代码段和内核空间数据段,在特权级3上定义用户空间代码段和用户空间数据段。以此来将操作系统与用户程序隔离开来,然后再通过为每一个进程创建不同的页表来将不同的进程彼此隔离开来。图7展示了分段与分页是如何结合起来运行的。

图6:逻辑地址,线性地址(虚拟地址),物理地址

图7:分段与分页机制

多级页表

在早期32位时代广泛使用的是两级页表模式如图8图9所示,而现代64位 CPU多采用四级页表模式如图10,图11,图12所示。为什么需要多级页表呢?主要是为了节约内存。我们可以做一个简单计算。假设我们在一个32位的系统使用一级页表,每页占_4K_大小,那么为了寻址_4G_空间,我们需要_1M_个页表项,如果每个页表项占4个字节,那么我们需要一共_4M_的大小来存储页表。假如我们有100个程序,每个程序使用不同的页表,那么则一共需要_400M_来存储页表。而如果使用二级页表,第一级页表需要_4K_大小,可以存储_1K_个页目录项,每个页目录项指向一个_4K_大小第二级页表,则我们一共需要4K+4M的大小。这岂不是比一级页表使用的_4M_要多了吗?事实上,得益于计算机理论中的局部性原理,第二级页表并不需要全部都存在,可以按需分配,4K+4M只是最坏的情况,最少的情况下我们只需要4K+4K内存,这样100个程序在最少的情况下只需要_800KB_,这比一级页表的_400M_要小得多。对于100个程序而言,两级页表需要的内存介于800KB ~ 400K+400M之间,大多数情况下远小于_400M_。 此外,使用多级页表的好处还在于,两级及其以后的页表可以不存放在内存而存放于磁盘,需要的时候再交换回内存。

图8:采用4KB页面大小的两级页表模式

图9:采用4MB页面大小的两级页表模式

图10:采用4KB页面大小的四级页表模式

图11:采用2MB页面大小的四级页表模式

图12:采用1GB页面大小的四级页表模式

ASOR用到了四级页表模式,具体映射如图13所示,静态映射是程序初始化时就建立好的页表,而动态映射是按需建立和删除页表。

图13:内存映射

4. 中断与异常

中断作为一种异步机制被广泛采用。理解中断主要是理解两部分内容,即,中断控制器如何发送中断给CPU以及CPU收到中断后如何处理。前者主要涉及,PIC, APIC (LAPIC, I/O APIC), MSI/MSI-X,后者主要就是IDT中断描述表。我们这里暂时只讨论后者。

IDTR是一个寄存器,存有IDT表的基地址和长度。当CPU从中断控制器收到中断号后,便可以通过IDTR来找到IDT中对应的中断门(一个8个字节的描述符,如图14)。通过这个中断门可以在全局描述符中找到对应的描述符,最后找到对应的中断处理函数的逻辑地址,再结合前面的分段与分页的机制找到中断处理函数的实际物理地址跳转执行。具体流程如图15所示。

图14:中断/陷阱门

图15:CPU收到中断后处理流程

x86体系结构一共有256个中断号(0 ~ 255),其中 (0 ~ 31) 保留给体系结构定义的异常使用,见表 2,(32 ~ 255)为用户自定义外部中断。异常一般可以分为四种,即中断(Interrupt),陷阱(Trap),故障(Fault),终止(Abort),其区别如表 1所示。

表1:异常的种类及比较

表2:x86保留中断号

一个简单的外部中断软件处理流程通常会是如图16所示。首先定义一系列IRQ(N)函数,将这些函数的地址填写到中断描述表对应IDT(N)位置。这些IRQ(N)有着差不多的实现,可以用一个统一的模板来定义,主要就是保存调用前的必要的上下文,调用一个通用的处理函数假设叫做_common_handler_,这个函数里需要完成一些通用的处理比如_send EOI_,除此之外,最重要的就是调用handler(N)handler(N)是用户通过类似register_irq(N, handler)的用户接口注册的处理函数。最后还需要恢复调用前的上下文继续执行。对于其他异常也是类似的流程。

图16:中断软件处理

5. x86虚拟化扩展(VMX)

我们知道,只有特权指令才能运行在ring0上。在引入虚拟化之后,guest操作系统同样也存在ring0 ~ ring3。然而按照概述中可虚拟化架构的定义,guest操作系统的ring0显然不能有运行特权指令的权限。x86对此给出的解决方案是引入root和non-root模式(见图17)。guest操作系统运行在non-root模式下,虚拟机管理软件运行在root模式下。这样即便是运行在non-root模式下的ring0的指令,也可以由硬件截获并重新加以模拟。在VM中执行特权指令时,触发VM Exit回到root模式,特权指令执行完之后通过VM Entry进入Non-Root的guest继续执行。

图17:Root / Non-Root 模式

VMX一共引入了13条指令如表3,包括5条指令用于控制管理VMCS结构(_VMPTRLD, VMPTRST, VMCLEAR, VMREAD, VMWRITE_),4条指令用于管理VMX操作(_VMLAUNCH, VMRESUME, VMXON, VMXOFF_), 2条指令用于TLB管理(_INVEPT, INVVPID_), 2条指令供Guest软件调用VMM服务使用(_VMCALL, VMFUNC_)。

表3:VMX指令

VMM软件的生命周期如图18所示

  • 软件通过执行VMXON指令进入VMX操作。
  • VMM软件可通过执行VMLAUNCH或者VMRESUME发生VM entry进入Guest,在Guest发生VM exit后VMM软件重新获得控制权。
  • VMM软件可以在VMCS结构中指定进入Guest的入口点以及Guest发生VM exit的入口点,VMM在执行完VM exit的相应处理后可以重新通过VM entry进入Guest。
  • VMM软件可以决定是否退出VMX操作,通过运行VMXOFF指令来实现。

图18:VMM软件生命周期

具体软件处理流程如图19所示。当执行vmx_run()后,就会运行Guest代码,如果运行Guest代码发生VM EXIT,便针对具体原因做相应处理。

图19:VMM软件处理流程

6. 虚拟机控制结构 (VMCS)

虚拟机控制结构(VMCS,图20), 是x86硬件辅助虚拟化提供的用于软硬件交互的数据结构。这块区域又被细分为六块子区域,分别是:

  • 客户机状态区域(Guest-state area): 当Guest发生VM EXIT时,会将当前的一些状态信息存储到这个区域,当下次VM Entry时,从这里加载状态信息继续运行。
  • 主机状态区域(Host-state area): 当Guest发生VM EXIT进入VMX-root时,从这块区域加载运行状态信息开始运行。
  • 虚拟机执行控制域(VM-execution control fields):控制处理器在VMX non-root模式下的行为,决定是否产生VM exits。
  • 虚拟机退出控制域(VM–exit control fields): 控制VM exits时的行为。
  • 虚拟机进入控制域(VM–entry control fields): 控制VM entry时的行为。
  • 虚拟机退出信息域(VM-exit information fields): 包含虚拟机退出的原因。

虚拟机执行控制域, 虚拟机退出控制域,虚拟机进入控制域有时也被统称为VMX控制域

图20:VMCS结构

在首次执行客户机代码之前,可以通过客户机状态区域中的RIP,来指定客户机代码的起始执行地址。

7. 虚拟化地址转换扩展 (EPT)

早期的地址访问虚拟化通常通过影子页表的方式来实现。所谓影子页表,也就是对每一个客户机的页表在VMM中建立一个与之对应的影子页表。如图21所示。

图21:影子页表

由于影子页表的虚拟化地址转换在其实现上的复杂性以及大量的VM exits和TLB flush导致的性能问题,现代虚拟化系统已经不再使用影子页表,取而代之的是硬件辅助扩展分页(Extended Page Table)。客户机操作系统维护一张页表用于将客户机虚拟地址(GVA)转换为客户机物理地址(GPA),VMM软件维护一张页表用于将客户机物理地址转(GPA)换为真正的物理地址(HPA)。当发生内存访问时,客户机可以通过这两张表直接获得真正的物理地址。其原理如图22所示。

图22:EPT原理

// 待续

参考

查看原文

赞 0 收藏 0 评论 0

calinyara 发布了文章 · 2020-06-01

dtop: 一个系统占用率及系统性能测量工具

top / htop等CPU占用率查看工具是通过累计系统时钟中断tick数目的方法来实现的。当在一个时钟中断周期内(1/Hz) 发生了多次进程调度的时候,这种 “加法” 的统计方法就会变得不准确。

图1: CPU占用统计不准确的例子

按照Linux内核CPU占用率的“加法” 方法。在上面例子的第1次和第2次时钟中断期间,内核漏掉了进程B的运行。在第2次和第3次中断期间,内核又漏掉了进程A的运行。

dtop是一个基于RUST语言编写的工具。其设计是基于 “减法“原理。dtop会在系统中每个CPU上运行一个“浸泡”线程,占据全部CPU。当有某些workloads被调度运行时,CPU算力被从这些“浸泡”线程让渡。“浸泡”线程所减少的CPU占用则为这些workloads实际占用的CPU资源。该测量方法十分精确,有效地避免了调度时间间隔小于系统时钟中断间隔造成的统计不准确。

References:

查看原文

赞 0 收藏 0 评论 0

calinyara 发布了文章 · 2020-06-01

Play with crosvm

Background

Crosvmis a device model based onRustlanguage.Chrome OSusesCrosvmalong withKVMto provide virtualization solution.

Install Rust and Dependencies

sudo apt-get install -y libcap-dev libfdt-dev
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env 

BuildCrosvm

git clone https://github.com/calinyara/crosvm.git
cd crosvm
./build.sh

Compiledcrosvmwill be located in crosvm/target/release/.

InstallCrosvm

sudo cp minijail/libminijail.so /usr/lib
sudo cp crosvm/target/release/crosvm /usr/bin

Build Kernel and Rootfs with Buildroot

git clone https://github.com/buildroot/buildroot.git
cd buildroot
wget https://raw.githubusercontent.com/calinyara/crosvm/master/configs/buildroot.config
mv buildroot.config .config
cd board/pc
wget https://raw.githubusercontent.com/calinyara/crosvm/master/configs/linux.config-5.3.x-x86_64
cd ../..
make

CompiledbzImageandrootfs.ext4will be located in output/images.

Try Crosvm

Start a VM, login with root without password

crosvm run --disable-sandbox --rwroot rootfs.ext4 -s crosvm.sock bzImage

Stop the VM, in another shell window run following command

crosvm stop crosvm.sock
查看原文

赞 0 收藏 0 评论 0

calinyara 发布了文章 · 2020-05-31

Armv8架构虚拟化介绍

1 综述

本文描述了Armv8-A AArch64的虚拟化支持。包括stage 2页表转换,虚拟异常,以及陷阱。本文介绍了一些基础的硬件辅助虚拟化理论以及一些Hypervisor如何利用这些虚拟化特性的例子。文本不会讲述某一具体的Hypervisor软件是如何工作的以及如何开发一款Hypervisor软件。通过阅读本文,你可以学到两种类型的Hypervisor以及它们是如何映射到Arm的异常级别。你将能解释陷阱是如何工作的以及其是如何被用来进行各种模拟操作。你将能描述Hypervisor可以产生什么虚拟异常以及产生这些虚拟异常的机制。理解本文内容需要一定基础,本文假定你熟悉ARMv8体系结构的异常模型和内存管理。

1.1 虚拟化简介

这里我们将介绍一些基础的Hypervisor和虚拟化的理论知识。如果你已经有一定的基础或是已经熟悉了这些概念,可以跳过这部分内容。我们用Hypervisor这个词来定义一种负责创建,管理以及调度虚拟机(Virtual Machines, VMs)的软件。

虚拟化为什么重要

虚拟化是一种在现代云计算和企业基础架构中广泛使用的技术。开发人员用虚拟机在一个硬件平台上运行多个不同的操作系统来开发和测试软件,以避免对主计算环境造成可能的破坏。虚拟化技术在服务器上非常流行,大多数面向服务器的处理器都需要支持虚拟化功能,这是因为虚拟化能给数据中心服务器带来如下一些需要的特性:

  • 隔离:利用虚拟化可以对同一个物理核上运行的虚拟机进行隔离。这使得相互间不可信的的计算环境可以共享同一套硬件环境。例如,两个竞争对手可以共享同一个物理机器而又不能访问对方的数据。
  • 高可用性: 虚拟化可以在不同的物理机器之间无缝透明地迁移负载。这个技术广泛用于将负载从出错的硬件平台迁移至其他可用平台,以便维护和替换出错的硬件而不影响服务。
  • 负载均衡: 为了降低数据中心硬件和功耗成本,需要尽可能充分地利用硬件平台资源。将负载均衡地迁移到不同地物理机上,有利用充分利用物理机资源,降低功耗,同时为租户提供最佳性能。
  • 沙箱:虚拟机可以作为一个沙箱来为运行在其中的应用屏蔽其他软件的干扰,或者避免其干扰其他软件。例如在虚拟机中运行特定软件,可以避免该软件的bug或病毒导致物理机器上的其他软件损坏。

1.2 Hypervisor的两种类型

Hypervisor通常被分成两种类型,独立类型Type 1和寄生类型 Type 2。我们先看看Type 2类型的Hypervisor。对于Type 2类型的Hypervisor,其寄生的宿主操作系统拥有对硬件平台和资源(包括CPU和物理内存…)的全部控制权。下图展示了Type 2类型的Hypervisor。

图1:Type 2 Hypervisor

宿主操作系统,指的是直接运行在硬件平台上并为Type 2类型的Hypervisor提供运行环境的操作系统。这类Hypervisor可以充分利用宿主操作系统对物理硬件的管理功能,而Hypervisor只需提供虚拟化相关功能即可。不知你是否使用过Virtual Box或是VMware Workstation, 这类软件就是Type 2类型的Hypervisor。

接下来,看看独立类型的Type 1 Hypervisor, 如图2。 这类Hypervisor没有宿主操作系统。其直接运行在物理硬件之上,直接管理各种物理资源,同时管理并运行客户机操作系统。

图2:Type 1 Hypervisor

在开源社区常见的Hypervisor, Xen (Type 1) 和 KVM (Type 2)就分属这两种不同的类型。其他开源的或知识产权的Hypervisor,可参见WiKi

1.3 全虚拟化和半虚拟化

关于虚拟机,经典定义是:虚拟机是一个独立的隔离的计算环境,这种计算环境让使用者看起来就像在使用真实的物理机器一样。尽管我们可以在基于ARM的硬件平台上模拟真实硬件,但这通常不是最有效的做法,因此我们常常不这么做。例如,模拟一个真实的以太网设备是非常慢的,这是因为对任何一个模拟寄存器的访问都会陷入到Hypervisor当中进行模拟。比起直接访问物理寄存器来说,这种操作的代价要昂贵得多。一个替代方案是修改客户操作系统,使之意识到自身运行在虚拟机当中,通过在Hypervisor中模拟一个虚拟设备来给客户机使用。以此来换取更好得I/O性能。严格来说,全虚拟化需要完全模拟真实硬件,性能上会比较差。开源项目Xen推进了半虚拟化,通过修改客户机操作系统的核心部分使其更适合在虚拟环境中运行,以此来提高性能。

另一个使用半虚拟化的原因是早期的体系结构并不是为虚拟化而设计的,存在虚拟化漏洞。因为虚拟化要求所有敏感指令或访问敏感资源的指令都能被截获模拟。对于存在虚拟化漏洞的体系结构,则需要通过半虚拟化的方案来填补漏洞。而今,大多数体系机构都支持硬件辅助虚拟化,包括Arm。这使得操作系统的核心部分无需修改也能获得较好得性能。只有少数存储和网络相关的I/O设备仍然采用半虚拟化的方案来改善性能,这类半虚拟化的方案如,virtio 和 Xen PV Bus。

1.4 虚拟机(VM)和虚拟CPU (vCPU)

有必要区分虚拟机(VM)和虚拟CPU(vCPU)。这有利于理解本文的后续部分。例如,一个内存页面可以分配给一个虚拟机,因此所有属于该VM的vCPUs都可以访问它。而一个虚拟中断只是针对某个vCPU,因此只有该vCPU可以收到。虚拟机(VM)和虚拟CPU(vCPU)的关系如图3所示。

图3:VM vs vCPU

注意:ARM体系结构定义了处理单元(Processing Element, PE)一词,现代CPU可能包含多个内核或线程,PE用来指代单一的执行单元。同样的这里的vCPU严格来说应该是vPE。

2 AArch64的虚拟化

对于ARMv8, Hypervisor运行在EL2异常级别。只有运行在EL2或更高异常级别的软件才可以访问并配置各项虚拟化功能。

  • Stage 2转换
  • EL1/0指令和寄存器访问
  • 注入虚拟异常

安全状态和非安全状态下的异常级别及可运行的软件如图4所示

图4:AArch64的虚拟化

注意:安全状态的EL2用灰色显示是因为,安全状态的EL2并不总是可用,这是Armv8.4-A引入的特性。

2.1 Stage 2 转换

什么是Stage 2 转换

Stage 2 转换允许Hypervisor控制虚拟机的内存视图。具体来说,其可以控制虚拟机是否可以访问特定的某一块物理内存,以及该内存块出现在虚拟机内存空间的位置。这种能力对于虚拟机的隔离和沙箱功能来说至关重要。这使得虚拟机只能看到分配给它自己的物理内存。为了支持Stage 2 转换, 需要增加一个页表,我们称之为Stage 2页表。操作系统控制的页表转换称之为stage 1转换,负责将虚拟机视角的虚拟地址转换为虚拟机视角的物理地址。而stage 2页表由Hypervisor控制,负责将虚拟机视角的物理地址转换为真实的物理地址。虚拟机视角的物理地址在Armv8中有特定的词描述,叫中间物理地址(intermediate Physical Address, IPA)。

stage 2转换表的格式和stage 1的类似,但也有些属性的处理不太一样,例如,判断内存类型 是normal 还是 device的信息被直接编码进了表里,而不是通过查询MAIR_ELx寄存器。

图5:地址转换, VA to IPA to PA

VMID

每一个虚拟机都被分配一个ID号,称之为VMID。这个ID号用于标记某个特定的TLB项属于哪一个VM。VMID使得不同的VM可以共享同一块TLB缓存。VMID存储在寄存器VTTBR_EL2中,可以是8或16比特,由VTCR_EL2.vs比特位控制,其中16比特的VMID支持是在armv8.1-A中扩展的,是可选的。需注意,EL2和EL3的地址转换不需要VMID标记,因为它们不需要stage 2转换。

VMID vs ASID

TLB项也可以用ASID(Address Space Identifier)标记,每个应用都被操作系统分配有一个ASID,所有属于同一个应用的TLB项都有相同的ASID。这使得不同应用可以共享同一块TLB缓存。每一个VM有它自己的ASID空间。例如两个不同的VMs同时使用ASID 5,但指的是不同的东西。对于虚拟机而言,通常VMID会结合ASID同时使用。

属性整合和覆盖

stage 1 和 stage 2映射都包含属性,例如存储类型,访问权限等。内存管理单元(MMU)会将两个阶段的属性整合成一个最终属性,整合的原则是选择更有限制的属性。且看如下例子:

图6:映射属性整合

在上面的例子中,Device属性比起Normal属性更具限制性,因此最终结果是Device属性。同样的原理,如果你将顺序调换一下也不会改变最终的属性。

属性整合在大多数情况下都可以工作。但有些时候,例如在VM的早期启动阶段,Hypervisor希望改变默认的行为,则可以通过如下寄存器比特来实现。

  • HCR_EL2.CD: 控制所有stage 1属性为Non-cacheable。
  • HCR_EL2.DC:强制所有stage 1属性为Normal,Write-Back Cacheable。
  • HCR_EL2.FWB (Armv8.4-A引入):使用stage 2属性覆盖stage 1属性,而不是使用默认的限制性整合原则。

模拟MMIO

与物理机器的物理地址空间类似,VM的IPA地址空间包含了内存与外围设备两种区域。如下图所示

图7:模拟MMIO

VM使用外围设备区域来访问其看到的物理外围设备,这其中包含了直通设备和虚拟外围设备。虚拟设备完全由Hypervisor模拟,如下图所示

图8:stage 2映射

一个直通设备被直接分配给VM并映射到IPA地址空间,这使得VM中的软件可用直接访问真实的物理硬件。一个虚拟的外围设备由Hypervisor模拟,其stage 2的转换项被标记为fault。虽然VM中的软件看来其是直接与物理设备交互,但实际上这一访问会导致stage 2转换fault,从而进入相应的异常处理程序由Hypervisor模拟。

为了模拟一个外围设备,Hypervisor需要知道哪一个外围设备被访问,外围设备的哪一个寄存器被访问,是读访问还是写访问,访问长度是多少,以及使用哪些寄存器来传送数据。

当处理stage 1 faults时,FAR_ELx寄存器包含了触发异常的虚拟地址。但虚拟地址不是给Hypervisor用的,Hypervisor通常不会知道客户操作系统如何配置虚拟地址空间的映射。对于stage 2 faults,有一个专门的寄存器HPFAR_EL2,该寄存器会报告发生错误的IPA地址。IPA地址空间由Hypervisor控制,因此可用利用此寄存器里的信息来进行必要的模拟。

ESR_ELx寄存器用于报告发生异常的相关信息。当loads或stores一个通用寄存器触发stage 2 fault时,相关异常信息由这些寄存器提供。这些信息包含了,访问的长度,访问的原地址或目的地址。Hypervisor可以以此来判断对虚拟外围设备访问的权限。下图展示了一个陷入(trapping) – 模拟(emulating)的访问过程。

图9:外围设备模拟

  1. VM里的软件尝试访问虚拟外围设备,这个例子当中是虚拟UART的接收FIFO。
  2. 该访问被stage 2转换block住,导致一个abort异常被路由到EL2。

    • 异常处理程序查询ESR_EL2关于异常的信息,如访问长度,目的寄存器,是load还是store操作。
    • 异常处理程序查询HPFAR_EL2,取得发生abort的IPA地址。
  3. Hypervisor通过ESR_EL2和HPFAR_EL2里的相关信息对相关虚拟外围设备作模拟,模拟完成后通过ERET指令返回vCPU,并从发生异常的下一条指令继续执行。

系统内存管理单元(System Memory Management Units, SMMUs)

到目前为止,我们只考虑了从处理器发起的各种访问。我们还需要考虑其他主设备如DMA控制器发起的访问。我们需要一种方法来扩展stage 2映射以保护这些主设备的地址空间。如果一个DMA控制器没有使用虚拟化,那它看起来应该如下图所示

图10:没有虚拟化的DMA访问

DMA控制器通常由内核驱动编程控制。内核驱动会确保不违背操作系统层面的内存保护原则,即一个应用不能使用DMA访问其没有权限访问的其他应用的内存。

下面让我们考虑操作系统运行在虚拟机中的场景。

图11:虚拟化下没有SMMU的DMA访问

在这个系统中,Hyperviosr通过stage 2映射来隔离不同VMs的地址空间。这是基于Hypervisor控制的stage 2映射表实现的。而驱动则直接与DMA控制器交互,这会产生两个问题:

  • 隔离:DMA控制器访问在虚拟机之间没有了隔离,这破坏了虚拟机的沙箱功能。
  • 地址空间: 利用两级映射转换,使内核看到的PAs实际上是IPAs。但DMA控制器看到的仍然是PAs。因此DMA控制器和内核看到的是不同的地址空间,为了解决这个问题,每当VM与DMA控制器交互时就需要陷入到Hypervisor中做必要的转换。这种处理方式是极其没有效率的,且容易出错。

解决的办法是将stage 2的机制推广到DMA控制器。这么做的话,这些主设备控制器也需要一个MMU,Armv8称之为SMMU(通常也称为IOMMU)。

图12:虚拟化下通过SMMU的DMA访问

Hypervisor负责配置SMMU,以使DMA控制器看到的物理地址空间与kenrel看到的物理地址空间相同。这样就能解决上述两个问题。

2.2 指令的陷入与模拟

有时Hypervisor需要模拟一些操作,例如VM里运行的软件试图配置处理器的一些属性,如电源管理或是缓存一致性时。通常你不会允许VM直接配置这些属性,因为这会打破隔离性,从而影响其他VMs。这就需要通过以陷入的方式产生异常,在异常处理程序中做相应的模拟。Armv8包含一些陷入控制来帮助实现陷入(trapping) – 模拟(emulating)。如果对相应操作配置了陷入,则这种操作发生时会陷入到更高的异常级别,便于Hypervisor模拟。

举个例子,执行等待中断指令WFI通过会使CPU进入低功耗状态。然而,当配置HCR_EL2.TWI==1时,如果在EL0/EL1执行WFI则会导致EL2的异常。 (:陷入不是为虚拟化而设计的,有陷入到EL3和EL1的异常,但异常对虚拟化实现至关重要。)

对于WFI的例子里, 操作系统通过在一个idle loop里执行WFI指令,但虚拟机中的操作系统执行该指令时,会陷入到Hypervisor里模拟,这时Hypervisor通常会调度另一个vCPU执行。

图13:WFI指令模拟

2.3 寄存器的访问

陷入 – 模拟的另一个用途是用来呈现虚拟寄存器的值。例如寄存器ID_AA64MMFR0_EL1是用来报告处理器内存相关特性的,操作系统可能会读取该寄存器来决定在内核中开启或关闭某些特性。Hypervisor可能会给VM呈现一个与实际物理寄存器不同的值。这是怎么实现的呢?首先Hypervisor需要开启对该寄存器读操作的陷入。然后,在陷入的异常处理中判断异常相关的信息并进行模拟。在如下例子中,就是设置一个虚拟的值,然后ERET返回。

图14:寄存器访问的陷入模拟

避免陷入

陷入 – 模拟的开销是很大的。这种操作需要先陷入到EL2,然后由Hypervisor做相应模拟再返回客户操作系统。对于某些寄存器如ID_AA64MMFR0_EL1,操作系统并不经常访问,陷入 – 模拟的开销还是可以接受的。但对于某些经常访问的寄存器以及性能敏感的代码,陷入太频繁会对系统性能造成很大影响。对于这些情况,我们需要尽可能地优化陷入

  • MIDR_EL1: 存有处理器类型信息
  • MPIDR_EL1:亲和性配置

Hypervisor可能希望在访问上述两个寄存器时不要总是陷入。对这些寄存器,Armv8提供了与其对应的不需要陷入的版本。Hypervisor可以在进入VM 时先配置好这些寄存器的值。当VM中读到 MIDR_EL1 / MPIDR_EL1时会自动返回VPIDR_EL2 / VMPIDR_EL2的值而不发生陷入。

  • VPIDR_EL2:读取MIDR_EL1返回VPIDR_EL2的值避免陷入
  • VMPIDR_EL2:读取MPIDR_EL1返回VMPIDR_EL2的值避免陷入

注意:VPIDR_EL2 / VMPIDR_EL2 在硬件reset后没有初始化的值,它们必须由软件启动代码初始化一个合理的值。

2.4 异常虚拟化

中断是硬件通知软件的机制,在一个使用虚拟化的系统中,中断处理会变得更为复杂。有些中断会由Hypervisor直接处理,有些中断被分配给了VM,需要由VM中的处理程序处理,并且还有可能在接收到这个中断时,对应的VM并没有被调度运行。这意味着我们不仅需要支持在EL2中直接处理中断,还需要一种机制能将收到的中断转发给相应VM的vCPU。Armv8提供了vIRQs, vFIQs, 和vSErrors来支持虚拟中断。这些中断的行为和物理中断(IRQs, FIQs, 和 SErrors)类似,只不过只有当系统运行在EL0/1是才会收到,运行在EL2/3是收不到虚拟中断的。

开启虚拟中断

虚拟中断也是根据中断类型控制的。为了发送虚拟中断到EL0/1, Hypervisor需要设置HCR_EL2中相应的中断路由比特位。例如,开启vIRQ,你需要设置HCR_EL2.IMO, 这意味着物理IRQ中断将被发送到EL2,同时虚拟中断将被发送到EL1。理论上,Armv8可以配置成VM直接接收物理FIQs和虚拟IRQs。但在实际应用中,通常配置VM只接收虚拟中断。

产生虚拟中断

有两种方式产生虚拟中断

  1. 配置HCR_EL2,由内部CPU核产生
  2. 使用GICv2及以上版本的外部中断控制器

我们先来看第一种机制,HCR_EL2中有如下的控制比特位

  • VI: 配置vIRQ
  • VF: 配置vFIQ
  • VSE: 配置vSError
    设置上述比特位等同于中断控制器向vCPU发送中断信号。和常规物理中断一样,虚拟中断受PSTATE控制。这种机制简单易用,但有个明显的缺点,需要由Hypervisor来模拟中断控制器的相关操作,一系列的陷入 – 模拟将带来性能上的开销。

第二种方式是使用Arm的通用中断控制器(Generic Interrupt Controller, GIC)来产生虚拟中断。从GICv2版本开始,GIC可以通过物理CPU interface 和 虚拟CPU interface发送物理中断和虚拟中断。见下图:

图15:GIC中断发送

这两个CPU interface是等同的,区别是一个发送物理中断信号,另一个发送虚拟中断信号。Hypervisor可以将虚拟CPU interface映射给VM,以便VM可以直接和GIC通信。这种方式的好处是Hypervisor只需建立映射,不需要做任何模拟,从而提升了性能。(PS:虚拟化性能提升的关键就在优化陷入,减少次数,优化流程

中断转发给vCPU的例子

上面介绍了虚拟中断是如何开启和生产的。让我们来看一个中断转发给vCPU的例子。考虑一个物理外围设备,该设备被分配给了某个VM,如下图所示:

图16:虚拟中断转发的例子

具体步骤如下:

  1. 物理外围设备发送中断信号给GIC。
  2. GIC产生物理中断异常,可能是IRQ或FIQ。由于配置了HCR_EL2.IMO/FMO,这些异常会被路由到EL2。Hyperviosr发现该设备已被分配给了某个VM,于是检查需要将该中断信号转发给哪个vCPU。
  3. Hypervisor配置了GIC将该物理中断以虚拟中断的形式转给某个vCPU。GIC于是发送vIRQ/vFIQ信号,如果此时还运行在EL2,这些信号会被忽略。
  4. Hypervisor将控制权返还给vCPU。
  5. 处理器运行在EL0或EL1,来自GIC的虚拟中断被接收(受PSTATE控制)。

上面的例子展示了如何将一个物理中断以虚拟中断的形式转发给VM。如果是一个没有物理中断对应的纯虚拟中断,Hypervisor可以直接注入虚拟中断。

中断屏蔽

我们知道中断屏蔽比特位PSTATE.I, PSTATE.F, PSTATE.A分别对应IRQs, FIQs和SErrors。如果运行在虚拟化环境中,这些比特位的工作方式有些许不同。

例如,对于IRQs,设置HCR_EL2.IMO意味着

  • 物理IRQ路由至EL2
  • 对EL0/EL1开启vIRQs

这同时也改变了PSTATE.I 屏蔽的含义, 当运行在EL0/EL1是,如果 HCR_E2.IMO==1, PSTATE.I针对的是虚拟的vIRQs而物理的pIRQs。

2.5 时钟虚拟化

Arm体系结构中,每个处理器上都有一组通用时钟。通用时钟由一组比较器组成,用来与系统计数器比较。当比较器的值小于等于系统计数器时便会产生时钟中断。在下图中,我们可以看到系统中通用时钟由黄色框部分组成。

图17:通用时钟比较器与系统计数模块

下图展示了虚拟化系统中运行两个vCPU的时序。

图18:Hypervisor运行有两个vCPU的时序

物理世界的时间(墙上时间)4ms里,每个vCPU各运行了2ms。如果我们设置vCPU0的比较器在T=0之后的3ms产生一个中断,那么你希望实际在哪个墙上时间点产生中断呢?是vCPU0的虚拟时间的2ms,也就是墙上时间3ms那个点还是 vCPU0虚拟时间3ms的那个点?

实际上,Arm体系结构同时支持上述两种设置,这取决于你使用何种虚拟化方案。让我们看看这是如何实现的。

运行在vCPU上的软件可以访问如下两种时钟

  • EL1物理时钟
  • EL1虚拟时钟

EL1物理时钟会与系统计数器模块直接比较,使用的是绝对的墙上时间。而EL1虚拟时钟与虚拟计数器比较。虚拟计数器是在物理计数器的基础上减去一个偏移。Hypervisor负责为当前调度运行的vCPU指定对应的偏移寄存器。这种方式使得虚拟时间只会覆盖vCPU实际运行的那部分时间。

图19:虚拟计数器的计算

下图展示了虚拟时间运作的原理

图20:虚拟时间原理

在一个6ms的时段里,每个vCPU分别运行了3ms。Hypervisor可以使用偏移寄存器来将vCPU的时间调整为其实际运行的时间。

2.6 虚拟化主机扩展(Virtualization Host Extensions, VHE)

图21显示了一个Type 1类型的虚拟化系统的软件栈与异常级别的对应关系,Hypervisor部分运行在EL2,VMs运行在EL0/1。

图21:Type 1虚拟化系统软件栈与异常级别

然而,对于一个Type 2类型的系统,其软件栈与异常级别的对应关系可能如图22所示

图22:VHE之前的Type 2虚拟化系统软件栈与异常级别

通常,寄主操作系统的内核部分运行在EL1,控制虚拟化的部分运行在EL2。然而,这种设计有一个明显的问题。VHE之前的Hypervisor通常需要设计成high-visor和low-visor两部分,前者运行在EL1,后者运行在EL2。分层设计在系统运行时会造成很多不必要的上下文切换,带来不少设计上的复杂性和性能开销。为了解决这个问题,虚拟化主机扩展 (Virtualization Host Extensions, VHE)应运而生。该特性由Armv8.1-A引入,可以让寄主操作系统的内核部分直接运行在EL2上。

将主机操作系统运行在EL2

VHE由系统寄存器HCR_EL2中的两个比特位控制

  • E2H:VHE使能位
  • TGE:当VHE使能时,控制EL0是Guest还是Host
|         Running in        | E2H | TGE |
|---------------------------|-----|-----|
|Guest kernel (EL1)         |  1  |  0  |
|Guest application (EL0)    |  1  |  0  | 
|Host kernel (EL2)          |  1  |  1* |
|Host application (EL0)     |  1  |  1  |

*当发生异常从VM退出到Hypervisor时,TGE将会初始化为0,软件需要先设置这一比特,再继续运行host kernel的主代码

一个典型的配置如下图

图23:E2H与TGE配置

虚拟地址空间

在VHE引入之前,EL0/1的虚拟地址空间看起来如下。EL0/1分两块区域,上面是内核空间,下面是用户空间。EL2只有一个空间,Hypervisor通常不需要运行应用,因此没有必要划分内核与用户空间。同理,EL0/1虚拟地址空间支持ASID,但EL2不需要支持。

图24:VHE之前的虚拟地址空间

当VHE引入之后,EL2可以直接运行操作系统代码。因此需要将地址空间划分和ASID的支持添加进来。同样,通过设置HCR_EL2.E2H来解决。

图25:开启E2H时的EL2虚拟地址空间

当运行在EL0时,HCR_EL2.TGE控制使用EL1还是EL2空间,当应用运行在Guest OS (TGE==0)为前者,运行在Host OS(TGE==1)为后者。

重定向寄存器访问

除了会使用不同的地址空间映射,VHE还有一个问题需要解决,那就寄存器访问。运行在EL2的内核仍然会尝试访问*_EL1的寄存器。为了运行无需修改的内核,我们需要将EL1的寄存器重定向到EL2。当你设置E2H后,这一切就会由硬件实现。

图26:E2H对系统寄存器访问的影响

但是,重定向又会带来一个新的问题,那就是Hypervisor完全可能在某些情况下,例如当执行任务切换时, 访问真正EL1的寄存器。为了解决这个问题,Arm架构引入了一种新的别名机制,以_EL12或_EL02结尾。如下例,就可以在ECH==1的EL2访问TTBR0_EL1。

图27:从EL2访问EL1寄存器

异常

通常系统寄存器HCR_EL2.IMO/FMO/AMO的这几个比特位可以用来控制物理异常被路由至EL1或EL2。当运行在EL0且TGE==1时,HCR_EL2路由比特将会被忽略,所有物理异常(除了那些由SCR_EL3控制的会被路由至EL3)全部路由到EL2。这是因为Host OS里运行的应用是Host OS的一部分,而Host OS运行在EL2。

2.7 嵌套虚拟化

Hypervisor可以运行在VM中,这称之为嵌套虚拟化。

图28:嵌套虚拟化

我们将第一个Hypervisor称为Host Hypervisor,VM中运行的Hypervisor称为Guest Hypervisor。

在Armv8.3-A之前,Guest Hypervisor可以运行在EL0。但这种设计需要大量软件模拟,不仅软件开发困难,性能也很差。Armv8.3-A增加了一些新的特性,可以让Guest Hypervisor运行在EL1。而Armv8.4-A引入的一些新特性,使得这一过程更有效率,虽然仍然需要Host Hypervisor参与做一些额外的工作。

Guest Hypervisor访问虚拟化控制接口

我们并不希望Guest Hypervisor能直接访问虚拟化控制接口,因为这么做会破坏VM的沙箱机制,使得虚拟机能够看到Host平台的信息。当Guest Hypervisor运行在EL1,并访问虚拟化控制接口时,HCR_EL2中新的控制比特位可以使这些操作陷入到Host Hypervisor(EL2)以便模拟。

  • HCR_EL2.NV:开启硬件辅助嵌套虚拟化
  • HCR_EL2.NV1:开启额外需要陷入的操作
  • HCR_EL2.NV2:开启重定向到内存
  • VNCR_EL2:当NV2==1时,指向一个内存中的结构体

Armv8.3-A添加了NV和NV1控制比特。在此之前,从EL1访问*_EL2寄存器时的行为是未定义的,通常是会产生一个EL1的异常。而控制比特NV和NV1使得这种访问可以被陷入到EL2。这就使得Guest Hypervisor可以运行在EL1,同时由运行在EL2的Host Hypervisor来模拟这些操作。NV还会导致EL1运行ERET陷入到EL2。

下图展示了Guest Hypervisor如何创建并启动虚拟机

图29:Guest Hypervisor创建并启动虚拟机

  1. 从EL1访问*_EL2寄存器将导致Guest Hypervisor陷入到EL2。Host Hypervisor记录Guest Hypervisor创建的相关配置。
  2. Guest Hypervisor尝试进入其创建的虚拟机,此时ERET指令会陷入到EL2。
  3. Host Hypervisor根据Guest Hypervisor的配置,设置相关寄存器以便启动VM,清理掉NV比特位,最后进入Guest Hypervisor创建的Guest运行。

按上述的方法, 在Guest Hypervisor访问任何一个*_EL2寄存器时都会发生陷入。切换操作如 任务切换,vCPU切换,VMs切换都会访问大量寄存器,每次陷入都会导致异常的进入与返回,从而带来严重的陷入 – 模拟性能问题。(回忆前面的内容,虚拟化性能提升的关键就在优化陷入,减少次数,优化流程)。Armv8.4-A提供了一个更好的方案,当NV2被设置时,从EL1访问*_EL2寄存器将会被重定向到一块内存区域。Guest Hypervisor可以多次读写这块寄存器区域而不发生陷入。只有当最后运行ERET时,才会陷入到EL2。而后,Host Hypervisor可以从该内存区域中提取相关配置并代Guest Hypervisor执行相关操作。

图30:Guest Hypervisor创建并启动虚拟机优化

  1. 从EL1访问*_EL2寄存器将会被重定向到一块内存区域,该内存区域的地址由Host Hypervisor在VNCR_EL2中指定。
  2. Guest Hypervisor尝试进入其创建的虚拟机,此时ERET指令会陷入到EL2
  3. Host Hypervisor从内存中提取配置信息,设置相关寄存器,以便启动VM,清理掉NV比特位,最后进入Guest Hypervisor创建的Guest运行。

这个改进方法相比之前的方法会减少陷入到Host Hypervisor的次数,从而提升了性能。

2.8 安全世界虚拟化

虚拟化扩展最早是在Armv7-A引入的。在Armv7-A中的Hyp模式等同于AArch32中的EL2,仅仅在非安全世界才存在。作为一个可选特性,Armv8.4-A增加了安全世界下EL2的支持。支持安全世界EL2的处理器,需配置EL3下的SCR_EL3.EEL2比特位来开启这一特性。设置了这一比特位,才允许使用安全状态下的虚拟化功能。

在安全世界虚拟化之前,EL3通常用于运行安全状态切换软件和平台固件。然而从设计上来说,我们希望EL3中运行的软件越少越好,因为越简单才会更安全。安全状态虚拟化使得我们可以将平台固件移到EL1中运行,由虚拟化来隔离平台固件和可信操作系统内核。下图展示了这一理念

图31:安全世界的虚拟化

安全EL2与两个IPA空间

Arm体系结构定义了安全世界和非安全世界两个物理地址空间。在非安全状态下,stage 1转换的的输出总是非安全的,因此只需要一个IPA空间来给stage 2使用。然而,对于安全世界,stage 1的输出可能时安全的也能是非安全的。Stage 1转换表中的NS比特位控制使用安全地址还是非安全地址。这意味着在安全世界,需要两个IPA地址空间。

图32:安全世界的IPA地址空间

与stage 1表不同,stage 2转换表中没有NS比特位。因为对于一个特定的IPA空间,要么全都是安全地址,要么全都是非安全的,因此只需要由一个寄存器比特位来确定IPA空间。通常来说,非安全地址经过stage 2转换仍然是非安全地址,安全地址经过stage 2转换仍然是安全地址。

3 虚拟化的损耗

虚拟化的损耗主要在于虚拟机和Hypervisor切换需要保存和恢复寄存器。Armv8系统中,最少需要对如下寄存器做处理

  • 31 x 64-bit通用寄存器(x0…x30)
  • 32 x 128-bit浮点/SIMD寄存器(V0…V31)
  • 两个栈寄存器(SP_EL0, SP_EL1)

使用LDP和STP指令,Hypervisor需要运行33条指令来存储和恢复这些寄存器。虚拟化最终的损耗不仅取决于硬件还取决于Hypervisor的设计。

查看原文

赞 1 收藏 1 评论 0

calinyara 关注了用户 · 2020-05-31

编程码农 @onlythinking

Life is so short, do something to make yourself happy, such as coding.

关注 543

calinyara 关注了用户 · 2020-05-31

一线码农 @huangxincheng

关注 1555

calinyara 关注了用户 · 2020-05-31

就想叫yoko @notokoy

关注 305

calinyara 关注了专栏 · 2020-05-31

边城客栈

全栈技术专栏

关注 3172

calinyara 关注了专栏 · 2020-05-31

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 63108

认证与成就

  • 获得 2 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-05-31
个人主页被 577 人浏览