【阅读总结】Xen and the Art of Virtualization

这两天终于看完了Xen,开始整理笔记到博客上来,并做做总结,过两天再去实践一下Xen。才疏学浅,如果理解有误,请大家不吝赐教。

本文的翻译找到地址如下,希望本人对文章的阅读消化能够帮助到大家的理解:
https://www.hackhome.com/InfoView/Article_113992.html

0. 本文关键名词解释

VMM -> Virtual Machine Monitor,虚拟机监视器。

Xen -> 即是整体架构中的hypervisor,相较于GuestOS有最高权限。

Guest OS -> 指要运行到VMM之上的OS。
Domain -> Xen中的一个运行中的操作系统。
GuestOS和Domain的关系就像Program和Process关系一样。

UML:User Mode Linux,虚拟化水平性能测试的关键,测试用户态下程序跑的咋样。

1. Prequisites知识、概念总结

  这里对文中的一些Prerequisites知识进行总结,包含名词,传统方法等,这里结合我自己的理解记录如下:

  • Q:什么叫做VMM,什么又是hypervisor
    A:VMM全称叫做Virtual Machine Monitor,我们以前直接叫VMware这样的软件为虚拟机,在语义上实际上是不太对的,也应该叫做VMM才对,它作为硬件和操作系统的中介,其实是众多虚拟机的一个监控。

  • Q:什么是全虚拟化(Full Virtualization),半虚拟化(Para Virtualization),与现在DevOps所流行的Docker,LXC(Linux Container)有什么区别?
    A:三种虚拟化方式的不同在于其虚拟化工作所属层次、虚拟化的泛化力度等方面,简述如下:

    1. 全虚拟化的目的在于完全在硬件层以上建立VMM,大规模兼容操作系统,即可以当VMM不存在,安装虚拟机。
    2. 半虚拟化会尽可能的以较低修改代价来**迁移(port)**原有操作系统到VMM上,以支持更优的性能。
    3. 类Docker技术,处于操作系统层的虚拟化,进程级的轻量虚拟化技术,采用cgroup控制资源和namespace控制命名空间隔离进程,为内核所提供的支持。
  • Q:什么是crosstalk
    A:Crosstalk发生在多个实体(进线程,网络请求)争夺资源时产生的服务性能(QoS,Quality of Service)无法保障的问题,所以crosstalk通常与QoS写在一起,叫QoS crosstalk
      从应用层软件来看,例如网络服务器在接收到大量请求后,如何保证每个客户端得到了应得的响应性能?如果无法拆分整合这些流或请求(从web角度来看,不结合业务肯定较难),这里便需要合适的调度算法对请求排序,优化多路复用。Web服务器未响应某些请求事小,但是在VMM这里就是大问题,因为服务对象是OS。参考:https://www.cl.cam.ac.uk/research/srg/netos/projects/archive/pegasus/papers/jsac-jun97/node4.html

  • Q:全虚拟化如何实现?半虚拟化如何实现?
    A:全虚拟化包括软件辅助硬件辅助两个方面,下图是软件辅助下的全虚拟化,VMM会将本来操作系统内核所要执行的权限下的指令(需要陷入内核执行的代码)全都捕捉,然后再向下请求。
      为了实现全虚拟化所应该具有的泛化能力,基于软件辅助的VMM会拆解很多指令,即换为元指令的组合。所以全虚拟化的工作压力应该主要在于学习各个操作系统的特权指令,然后用元指令替换。
    PS:我们常用的VMware就是如此。
    在这里插入图片描述
      硬件辅助下的全虚拟化如下图所示:可以看出来HostOS不再需要,VMM成为了HostOS。区别在于VMM在硬件支持下可以直接将特权指令进行执行。这里可以想象一个VMM的设计,可以用一个HostOS加上支持虚拟化的内核模块,便成为了VMM。这样的VMM的代表性技术就是**KVM(kernel based virtual machine)**虚拟机,知道了它的实现方案,即可顾名思义。
    在这里插入图片描述
      半虚拟化的结构会对VMM进行功能的扩充,VMM会更加的像一个元操作系统,拥有更多的功能,同时它也需要对GuestOS进行修改,实现设计目的,Xen就是一个半虚拟化的成果。对于它的架构可以参考Xen架构图,Xen虚拟了CPU,内存,磁盘网络模块,建立了一个高级的OS。
    在这里插入图片描述

  • Q:x86架构由于本身设计并未太多考虑虚拟化的功能,留下了哪些坑?
    A:本文发布时,x86架构中的一些特权指令允许在Ring1下执行,导致在全虚拟化环境下的VMM难以捕捉这样的指令。后续有一些方法通过扫描GuestOS的二进制代码,强行捕获特权指令并翻译执行。
      VMware为了解决这个问题,就用了动态插入trap进行了解决,GuestOS也要进行相关修改。VMware在这样的状况下,更新操作会有很多开销,如更新页表,创建进程。

  • Q:什么是scatter gather DMA?
    A:首先DMA(Direct memory access)都很熟悉了,龟速的外设和高速的CPU,为了不要总是麻烦CPU处理外设的消息读入内存的问题,外设上通常也加了一个类-cpu的东西,用于将数据直接传给内存页。scatter指分散,gather指收集,对于内存页等存储单位上不连续的信息造成多次访问的问题,为了提高IO效率,提出了scatter gather DMA技术。查到一篇资料如下:
    How Does Scatter/Gather Work?: https://www.eejournal.com/article/20170209-scatter-gather/

  • Q:什么叫做differentiated service?
    A:直接翻译是差异化服务,在VMM当中大概是指与调度相关的工作,通过对服务进行分类、整合、限制的策略,最终提升QoS。

  • Q:VMM本身的作用是什么?
    A: 1)加强服务器能力,2)提供同节点部署的优势(几乎localhost下的消息传递当然高于分布式),3)分布式的仿真实现,4)安全的计算平台(沙盒),5)应用的portable

  • Q:VMM面临什么挑战?
    A:1)独立性挑战(避免一个OS影响了全部),2)不同操作系统,异构应用的共存,3)虚拟化低开销的实现

  • Q:一定要虚拟化OS吗?在一台OS上尝试运行各种应用不行吗?
    A:虚拟化的作用的不用说,巨大的配置量和复杂的软件关系,portable当然不会得到满足,更不必说应用之间资源分配,调度管理。当然如果类似网格计算等计算单元之间没有利益冲突的应用场景,那么虚拟化的必要还真就没有了。

  • Q:当下的容器技术如Docker不能干掉虚拟机吗?
    A:目前容器技术已经实现了资源隔离,在其目前存在的安全问题被解决之后或许可以干掉虚拟机。但是,容器间同样会发生的crosstalk问题,这类问题最好是在底层解决,比如,多个容器间的确设置了资源使用限制,实现了隔离,但是考虑到性能,底层的指令排序及CPU调度和大量的上下文切换带来的多余开销上层或许不可见。
      此外,两种技术的设计方向其实也不一样,LXC偏向于Devops的简洁性,向软件工程向微服务架构的发展,容器本身仍然只是一个进程,当容器内部需要加入更多的软件,容器间需要更方便的调试时,或许还是虚拟机方便点。此外还有一点,Docker可以继续运行在虚拟机之上~。
      对于这个问题,我觉得像是OS地址空间同时存在段划分和页划分一样。像船运里大集装箱和小集装箱一样,这两个技术并不是零和博弈。

2. 现有的虚拟化技术特点,Xen的不同

  本文提到现有的虚拟化技术的不足也就是虚拟化要解决的问题,优势与问题如下所示:

  • 虚拟化技术需要特别的硬件支持。(这个弱点显而易见,应用层软件都有前后端分离,这个是软硬端,必须解决)
  • 需要定制化的操作系统,商业操作系统支持程度不够
  • 速度快而安全性或功能性不能得到满足
  • 少数的虚拟化技术实现了资源隔离性能保障,资源分配上做文章的方法很多

  Xen通过对底层资源抽象,接收上层虚拟OS进程的**二进制接口 ABI(Application Binary Interface)**执行。其成绩达到了无性能、功能损失,商业OS如Linux,Xp,BSD都可以共存。实现100个OS同时运行,几乎可忽略的虚拟化开销,和与其它VMM技术在Benchmark上的性能突出对比。Xen特点总结如下:

  • GuestOS 的 ABI(Application Binary Interface)不用修改。
  • 全/多应用的操作系统
  • 实现了OS间资源隔离,在x86架构下
  • 完全隐藏了资源虚拟化面临的正确性和性能威胁。

  与Denali项目的区别:

  1. Denali实现的是在单用户单应用且处于特权态下的操作系统,Virtual MMU的实现或许可以帮助Denali改善这些弱点,但是当时还没有Virtual MMU成果。
  2. Denali使用VMM进行了所有的页管理,Xen认为,GuestOS应该可以独立处理自己的内存页,管理自己的信息。
  3. Denali建立了namespace,不再namespace之内的OS不可以互相访问,VM见到的都是虚拟出来的东西。Xen认为hypervisor已经足够做了安全检查的工作,让物理设备可被GuestOS访问具有更多益处。

3. x86架构下的半虚拟化接口设计

  本节从宏观概括的角度叙述Xen整体设计。

3.0 概述

  1. 内存方面:Xen对Guest OS的地址空间采取了分段操作,底层为Xen独享,然后是GuestOS的内核层,然后是Guest用户空间。

    • 分段:GuestOS 不可以拥有全部权限的段标记符
    • 分页:GuestOS允许看到硬件页表,但是不可以直接更新页表项。一个Domain拥有的页空间不一定连续。这里其实是又一层抽象,操作系统本身通过虚拟地址欺骗进线程,进线程陷入内核态更新页表。这里变成了VMM欺骗GuestOS。
  2. CPU方面:

    • 保护:Guest OS优先级低于Xen(Ring X)。
    • 异常:Guest OS需要注册Exception Handler到Xen中,缺页中断除外。(前面所述Xen的设计让GuestOS有管理内存的功能不被删除)。
    • 系统调用:允许Guest OS专门装一个handler处理系统调用,避免过多地通过Xen传递。
    • 中断:Guest OS对各个设备等的中断处理转化为由Xen的事件通知。
    • 时间与计时:GuestOS拥有timer接口,可以主动意识到真实时间和虚拟时间。
  3. Device IO方面:指磁盘网络等的传输

    • Xen采用异步IO环向各个Domain传输数据,异步IO环可见后续的具体实现 IO Rings。
    • 事件机制代替设备中断。

3.1 内存管理

  Xen内存管理需要hypervisorGuest共同进行修改。由于x86架构下使用了硬件支持下的TLB,CPU会主动检查TLB上页表信息,相较于软件辅助下的TLB可以对TLB项进行标记所属,基于硬件辅助的TLB在操作系统的上下文切换会不太方便。所以为了保障内存管理正确性,有两个基本要求:

  1. 当前地址空间上的页转换必须与硬件相符合
  2. TLB在每一次的地址空间切换后都要进行flush

  上面要求里1表达Xen不必再做地址翻译,2是处理x86下硬件TLB的问题。所以,Xen做了两个决策:

  1. GuestOS负责分配和管理硬件页表,如此最小化Xen对内存管理的介入,Xen做的只是空间隔离及安全的事情。
  2. Xen占据每个操作顶层64M的的地址空间,操作系统可以更加方便的同Xen进行信息交流,如此可以避免操作系统状态与Xen状态切换时不必要的TLB flush开销。(PS:这个操作类似于Improving Linux IPC by Kernel Desgin里的内核空间在进程空间里的设置,操作系统若在顶上的64M地址空间进行操作,也就类似于操作系统陷入内核态)

  Guest OS在其Page Table上的操作会被Xen检查,也就是说Xen即便不会干预各个Guest OS对其内存片区的操作,但是仍然拥有内存使用的全局视角,Xen参与相关过程如下:

  1. Guest OS对自己拥有的内存区块进行分配和初始化,需要在Xen中进行注册
  2. 所有内存的更新操作需要由Xen进行。(Xen接收Guest OS请求完成内存更新)

  以上是内存页相关的管理,对于段内存也是类似的,GuestOS始终有两个限制:

  1. 段描述符优先级低于Xen
  2. Xen的地址空间禁止访问

3.2 CPU

  Hypervisor的引入要求OS不再可以是拥有最高优先级的实体,所以GuestOS必须做出相应修改。处理器架构通常有两种权限,高权限给Xen,低的给OS。x86架构有权限环Ring,Xen处于Ring0,操作系统核心态Ring1,用户态Ring3。此外,由Xen执行所有的权限指令。

  在异常处理方面,Xen简单直接地将异常向量表放到Xen中,便于异常处理。特别的对于缺页中断Page Fault,由于该异常需要在Ring0下才能解决,因为需要访问CR2寄存器,所以Xen会帮助Guest OS读取CR2寄存器并将值返回给OS。

  如何保证异常处理的代码安全,Xen在这里只需要检查一下CPU的权限即可,因为Xen通过异常向量表可以获得异常处理的代码段,权限必为Ring0,所以不是Ring0下的异常处理视为非法即可。

  Xen也会记录错误程序错误次数,连续发生2次的错误的Guest OS会被终止。此外,Lazy-checking是合适的,即是说Xen没有必要对Guest OS发生的异常去主动捕捉,因为即便Guest OS启动了异常处理,异常处理也总是在Guest OS的地址空间,它的相关权限操作还是会被虚拟化。

3.3 设备IO

  在全虚拟化下,IO仿真了所有的硬件设备,而Xen提出采用简单干净的设备抽象。Xen上Domain之间的数据通信及Domain与设备之间的数据通信都由一片处于共享内存上的异步IO Buffer Descriptor Rings环结构维护,即5.2中的IO Rings。这个环方便了Xen对各个Guest OS的检查、验证。
  Xen还设计了Xen与GuestOS之间的事件机制,可以发送各种消息给各Domain,消息发送的更新基于一个bitmap。对于事件的回调,即操作系统对事件的处理仍然由各Domain自行决定。

4. Guest OS的Xen迁移开销

  XP的迁移开销大点,XP特别的在内存页管理部分的代码数据结构众多,Linux比较少。此外XP还存在很多16位的指令,需要对他们进行仿真。其它的修改包括对权限代码的处理。

5. 细节实现——控制与数据传递

5.1 控制传递 Control Transfer: Hypercalls and Events

  首先本文确定了Hypervisor要管什么不管什么,Xen的设计理念是提供最基础的操作支持,底层的Hypervisor应当维持其简洁的特性。

不管
Domain间CPU资源调度 资源如何分配
网络数据包的分发 网络数据包如何传递
数据块访问时的权限控制 ~

  由Xen架构图,有一个特殊的GuestOS,Domain 0,它是Xen的初始OS,提供了很多配置其它OS的能力,以管理软件为顶层接口,通过Control Interface进行资源配置,可以实现**VIF(Virtual network interface)的管理和VBDs(Virtual Block Devices)**的domain访问控制。

  Xen以异步的事件通知方式通知Domains,Domains通过hypercall请求Xen。轻量级的事件机制替代了操作系统本身关键的中断处理等状况,例如事件可被用于告知网络上新的数据接收完毕或磁盘请求完毕等。这个机制类似于传统的Unix 信号。

  每一个Domain当中有一个存放Pending Events(挂起未处理的事件)的bitmask,它由Xen更新,GuestOS针对事件进行处理,回调的处理会重新设置Pending Event。Domain可以选择推迟一些事件的处理,具体是设置Xen-readable software flag,这就类似真实处理器里关闭中断处理一样。

5.2 数据传递 IO Rings(IO环)

  IO Rings的设计支持数据在Xen体系结构的纵向层面实现数据流动,并尽可能降低传输开销。为了实现目标,提出了三个设计方向,也是虚拟化目标:

  1. 在解复用数据时尽量降低开销(即降低数据从物理设备到达具体Domain的开销)
  2. 尽可能有Domain提供内存用于设备数据映射,避免crosstalk在Xen的共享内存区域发生
  3. 数据传输过程中固定页表项,保护IO缓冲区。

  IORing由Domain所分配,Xen可以访问,环中的entry或者slot都是一个指针,指向具体的内存页或数据buffer。

  IO Ring的结构如下图所示,Xen消费了由GuestOS产生的请求,同时也就制造了回复。GuestOS拥有一个请求制造和一个选择消费。其中消费者指针均为private,制造者指针为shared,为什么如此?看一下图的结构其实很明显,消费者需要知道什么时候消费完了。
在这里插入图片描述
  Request本身不一定是按照队列顺序进行处理,GuestOS关联了request的标示,在每一次事件响应接收后都可以重新生成顺序。这样的结构通用性很强,可以支持不同的设备模式。重排序功能支持Xen调度任务,Descriptor与带外数据的使用(指针)方便了零传输拷贝(zero-copy transfer)。

  IO环将响应与通知进行了分离。Domain可以在请求Xen之前加入多个请求条目,还可以也可以设置阈值推迟通知,这个实现实际上允许了Domain自己去权衡吞吐量和服务延迟。

6. 细节实现——子系统虚拟化

  5中所述的技术在具体的子系统中都得到了体现,接下来对各个子系统的细节实现再做说明。

6.1 CPU调度

  CPU调度在虚拟化这里的主要工作是快速切换Domain上下文,这里使用了**Borrowed Virtual Time(BVT)**调度算法。每个Domain有关调度的参数在Domain0中均可设置。

6.2 时间和计时器Time & Timers

  Xen中有三种时间:

  1. Real Time:nanosecond为单位的计时,自从系统启动开始,辅助CPU,可用NTP进行时间校正。
  2. Virtual Time:Domain的计时器,记录他们真实运行的时间,辅助调度。
  3. Wall clock Time:用于给时间加偏移,或许是帮助应用的运行,具体应用暂时不知。

  每一个Domain都可以设置计时器Timer,可以使用Real Time和Virtual Time,到时Xen会通知它们。

6.3 虚拟地址转换

  如前面所述,x86使用的是硬件页表,VMware是通过给每个GuestOS提供一个新的虚拟页表,即再加上一层翻译解决这个问题,OS的MMU不知道真实内存状况。VMware用陷阱捕捉所有OS访问内存的请求,然后交予自己创建的虚拟页表转换找到真实的物理地址。

  VMware在硬件和OS中间再插上一层地址翻译的做法大大增加了Guest OS的开销,从语义上看,应该是加倍的,所以VMware在新地址空间的开辟,进程的创建时开销都很大。

  不过全虚拟化本身的确需要shadow page table的使用,为Guest OS展现出它好像拥有连续地址空间的样子。Xen同样得有一个shadow page table。但是它的的处理较为简单,它只在Domain更新时介入。所有GuestOS真实页都会注册到Xen的MMU里,并且限制GuestOS只拥有读权限,Domain如若要更新页表,便调用hypercall。每一个页表采用引用计数的方法和未被pinned的状态标识确定是否被废弃。

  每一个内存页框(frame)都关联了一个类型标识type引用计数type总共有如下六种,type是mutual exclusive的,即type间不存在包含关系,且只可以存在一个状态。

【TODO】以后专门再研究整理下内存管理中常见的这些元素

  1. page directory(PD)
  2. page Table(PT)
  3. Local descriptor Table(LDT)
  4. Global descriptor Table(GDT)
  5. writable(RW)

  类型系统约束了Domain无法主动更新页表,因为需要具有PT和RW两个同时存在。类型系统也被用于跟踪哪些frame已被验证,并可以在页表中使用,避免了在上下文切换后页表验证的工作。此外,Guest OS拥有对 frame Unpinned的能力(如在数据接收完毕,内存页属于Domain之后,Guest OS Unpin frame,frame引用计数+1)。

  为了降低hypercall的次数,Guest OS可以在本地排队请求到队列,在创建新地址空间时非常有用。为保障请求的正确性,Guest OS在第一次请求地址空间时会执行TLB的flush,确保缓存的地址映射都被清除,所以Guest OS在切换前必须确定TLB中的页都已经完成更新(指脏页的处理)。

6.4物理内存

  Domain的初始物理内存大小在创建后就会被分好,内存间提供严格隔离,每个domain有一个上限内存大小。如果某个domain内存压力上来之后,可以申请到上限,如果domain处于内存eco的状态,可以归还。

  ExnoLinux实现了Balloon Driver, 通过这个Driver实现Xen和XenoLinux通过page allocator实现内存页的传递。Balloon Driver简单地修改了Guest OS,可以降低迁移开销,当然半虚拟化允许对Guest OS进行其它修改,比如加入OOM(Out of Memory)的处理,发生后可以请求申请更多的空间。
这个Balloon Driver才是虚拟化下内存页交互的核心实现,后续需要去学习

  Guest OS永远都认为自己拥有连续的物理地址空间,然而经过Xen他们的空间就像进程空间一样,都是碎片。本文用到了两个词,physical addresshardware address,从上下文理解可知,前者是指GuestOS所认为的物理地址,后者是指真正的地址。Xen提供的是physical addresshardware address的转换,Xen通过一个Domain所共享的数组(下标索引就是物理地址)实现真实地址查询,整个过程都是Domain负责,Xen只提供最后一步的转换。
  以上这样的作法自然会方便Guest OS许多,它们只需要对底层的地址接口就行。此外还有两点是操作系统本身的设计,其关乎性能,所以对Guest OS完全虚构物理地址空间是不便于它们性能发挥的:

  1. 操作系统通常会根据物理地址,优化索引缓存(应该是指一项缓存吧,一次命中都会带回一行信息,那么这一行信息当然有用内容越多越好)
  2. 为了内存连续考虑(这个可以参考《Linux内核是如何分割你的RAM的(划分物理地址)》这篇文章知道操作系统slab,buddy分配方法的目的)

6.5 网络

  Xen提供了Virtual Firewall-Router(VFR)的抽象,每个Domain都有1至多个网络接口(VIF(Virtual network interface)),VIF类似网卡,一个VIF有两个相对应的IO Ring,一个用于接收消息,另一个用于发送。VFR支持Rule的加入,类似Iptable吧,管理网络流量的,实现防火墙。

  在传输过程中,Guest OS只需要把Buffer Descriptor加入到IO Ring中,Xen复制descriptor然后在rule的指导下执行任务,包的传输开销并不会到复制过程上,因为使用的是scatter-gather DMA(与DMA有关的技术,提升IO效率,大概是提高传输过程有效载荷,第一章QA也有)。

  在传输过程中,传输到的页桢必须为pinned的状态,保证物理页不被别的再访问。
  为了保证公平调度,Xen实现了个round-robin。
  为了高效实现数据接收,Guest OS每一次接收到数据都要交出一个未使用的页桢用于交换内存,这是为了避免Xen与Domain的内存复制,Xen会直接将IO Ring上的descriptor指向的数据页给Domain。

6.6 磁盘

  磁盘虚拟化中只有Domain0拥有直接访问磁盘的权限,其他Domain需要经过VBD(Virtual Block devices),VBD由Domain0上的管理软件进行配置。

  相较于其他复杂的实现,Xen较于简单,VBD中包含了Block所有者信息,消息的传输也是通过IO Ring进行。Guest OS的磁盘调度算法会在请求入环前进行排序(eg:电梯调度)减少响应时间以及实现差异化服务。Xen因为拥有全局的请求信息,所以Xen又会对磁盘请求重排序,返回结果不一定如Domain的意思。

  VBD就类似SCSI硬盘。VBD与真实硬盘之间存在一个Translate Table,在Domain 0中可通过privilleged control interface(架构图中可见)进行管理。数据传输的零拷贝与网络是一样的,交换page,DMA再直接送上。

7 新的Domain的创建

  新Domain的创建仍然是可通过Domain0来操作,Domain0继续用privilleged control interface访问新Domain的内存,并通知Xen的寄存器状态。
  这样的方法降低了hypervisor的复杂度,另外在Domain 0上的操作也可以帮助调bug方便,进而提升系统鲁棒性。

8 总结

  仅从文章来看永远都是空中楼阁啊~~接下来持续打基础,也是时候考虑实践实践了。好久也没敲代码了,不过看到操作系统层的那些C代码就觉得好难。。。

参考文章

浅谈Xen和半虚拟化技术