基于X86的Xen虚拟化的体系结构文档格式.docx
《基于X86的Xen虚拟化的体系结构文档格式.docx》由会员分享,可在线阅读,更多相关《基于X86的Xen虚拟化的体系结构文档格式.docx(14页珍藏版)》请在冰豆网上搜索。
XenLinux可以运行Domain0中的drivermodule程序。
Xen在支持泛虚拟化虚拟机的同时,也支持基于Intel-vt技术的硬件虚拟机(hvm),这些硬件虚拟机中的GuestOS是不用修改代码的,因为和VMM的交互由硬件捕获特定指令,然后提供新的CPU操作来标识是在虚拟机状态下,还是在真是机器,也就是在VMM管理状态下。
通过Xen这种高性能的虚拟机管理,计算机资源被以最小的代价划分,这样虚拟机也取代真实机器成为计算资源的基本单位,从而在统一调配资源时实现了概念性的质的飞跃。
2.Xen的设计思想和实现
图1(摘自参考文献[2])
泛虚拟化Xen接口
如图1中的DomainU:
Para-VirtualizationDomain是泛虚拟化域部分,泛虚拟化的Xen接口包含三大部分:
CPU,存储管理,设备I/O。
2.1.1CPU
设计思想:
虚拟化CPU对guestOS提出了主要两个方面要求:
其一,XenHypervisor(Xen的VMM名字叫XenHypervisor)插在操作系统的下层违背了惯常的关于操作系统在整个系统中特权最高的假设。
为了保护VMM不会受到操作系统不正确行为的影响(即domain不受另一个domain的影响),guestOS就必须被改造为能够运行在较低的特权级上。
x86架构上有效地实现特权级的虚拟化是可能的,因为x86架构在硬件上支持四个不同的特权级。
x86架构的特权级往往用环(ring)来表示,从ring0(最高特权)到ring3(最低特权)。
操作系统的代码运行在ring0这个特权级上,因为再没有其它的ring能够执行那些特权指令。
ring3通常用于执行应用代码。
在x86体系架构上,通过安排Domain的GuestOS运行在ring1特权级(修改GuestOS代码),这就防止了guestOS会直接执行特权指令,也保证了GuestOS(ring1)与运行在ring3上的应用程序之间相隔离的安全性,同时也保正GuestOS不正确行为也不会影响VMM。
其二,XenHypervisor提供GuestOS异常处理的服务(如缺页处理)。
因为运行在DomainU的GuestOS是在ring1特权级,所以对一些需要ring0特权的指令是无法处理的,因此需要GuestOS用Hypercall做请求(类似APP向OStrap,这里是GuestOs向VMM去trap,必须修改GuestOS代码,但是systemcall用其他处理方法),VMM在ring0特权级去做相应处理。
VMM用表记录每类异常要进行的操作处理(没VM时异常的操作处理记录在硬件异常列表)。
VMM表中给出的处理都是与真正的x86硬件中相同的;
之所以这一点是可能做到的,主要是因为泛虚拟化架构中,异常堆栈框架是没有被修改。
具体实现:
(xen-3.1.0-src/xen/arch/x86/x86_32/:
systmecall和缺页中断处理方法)
只有两类异常会经常发生而影响到系统的性能:
系统调用(一般都是通过软中断实现)和缺页pagefault。
对于系统调用:
每个guestOS都记录一个“快速的”异常操作处理程序来改进系统调用的性能。
这个异常操作处理程序可以直接由CPU使用,而不必非要间接地经过ring0;
这个处理程序在放置进硬件异常列表中之前就是经过确认的,所以不必经过VMM,换句话说,Systemcall可以在GuestOS内部完成。
对于缺页:
不可能使用同样的技术来处理缺页中断,因为只有那些运行在ring0的代码才能够从特权寄存器CR2(2号控制寄存器)中读出缺页的地址;
因此,缺页必须要经过VMM才能提交,VMM保存该寄存器的值供来自ring1的访问使用。
如图1,有两种机制用于Xen和其上的domain之间进行控制的交互:
使用hypercall产生从domain到Xen的同步调用;
使用异步事件(Event)机制完成从Xen到domain的通告递交。
hypercall接口允许domain通过执行一个同步软陷阱陷入到hypervisor执行一个特权操作,这类似于在传统的操作系统中对系统调用的使用(这通过修改GuestOS代码去做Hypercall请求实现,而系统调用则通过修改GuestOS代码运行在ring1特权级)。
举一个使用hypercall的例子:
一组页表更新的请求,要经过Xen确认并且完成相应的更新操作(更新是要由Xen确认并完成的,需要特权操作,所以这时要利用hypercall陷入到hypervisor中),在更新完成后再由Xen将控制返回给产生本次调用的domain。
从Xen到domain的通信是由一个异步事件(Event)机制提供的。
Xen通知每个DomainU(包括外部硬件中断)通过eventchannels,每个DomainU有1024个eventchannels,它们由在DomainU和VMM共享的一对内存页中的bit位组成。
FirstBit表示event待处理,SecondBit用来屏蔽event。
如果event待处理,VMM调度器会调用upcall进入DomainU。
注意由于中断控制器牢牢抓在VMM手里,所以相应信息VMM会从中断控制器读取。
VMM(Xen)提供了一系列函数接口XenAPI,它们是:
:
(可编程API包含内容)upcall在回复目标虚拟机内部行为时被调用
目标GuestOS磁盘读操作触发对应的diskReadfunction回复GuestOS
(可编程API包含内容)downcall是VMM
提供努力积极控制GuestOS的APIxmcreate命令启动GuestOS(是VMM
对GuestOS的控制,不是对GuestOS回复)
这个机制取代了常用的利用设备中断的递交机制,它允许那些重要事件(如domain-terminationrequest)采用轻量级的通告形式。
和传统的Unix信号类似,这些重要事件的个数比较少,但每一个都用作针对某一特定类型事件的标记。
例如,用于在网络上指出新的数据已经被接收到的事件,或者表示一个虚拟磁盘请求已经完成的事件。
那些未决的事件存放在每个domain的bitmask中。
bitmask的更新要由VMM在调用一个和guestOS相应的事件调用返回处理程序之前完成。
Xen针对某类事件要向上发通告,如果VMM调用了guestOS相应的事件调用返回处理程序,就说明该事件完成了,下面要把控制交回给domain,所以必然要在调用事件调用返回处理程序之前由VMM将bitmask更新。
调用返回处理程序负责重新设置未决事件集合(调用返回处理程序仍旧是由VMM操作,更新bitmask),同时以相应的行为和通告相呼应。
一个domain可以通过设置一个Xen可读的软件标记来显式地推迟对事件操作:
这一点是与在真实的处理器中禁止中断的过程类似的。
2.1.2存储管理
因为是虚拟机,所以GuestOS所得的内存地址是不连续的,GuestOS虚拟地址如何与真实的物理地址对应是一个很重要的问题。
Xen的存储管理设计的根本思想在于并不是所有的操作都需要对真实的物理地址操作,只有创建页表和页表的写操作才需要操作真实地址。
Xen泛虚拟化的Hypervisor和客户机共享同一个地址空间。
32位x86(非PAE模式)架构下的Hypervisor占虚拟内存4GB空间的高64MB空间,即从0XFC000000到0XFFFFFFFF,通过重定向泛虚拟化客户机的GDT表实现。
Hypervisor为每一个虚拟域准备一个内部GDT表,以容纳客户机GDT,但同时为Xen保留一部分描述符空间用于Hypervisor将自身的代码和数据分别映像到nng0、1和3,同时映像每一个CPU的TSS(任务状态段),映像每一个CPU的LDT。
因此,泛虚拟化的Xen客户机只能使用0到4GB-64MB的地址空间,并不会影响Linux应用程序的正常执行(应用程序只使用0到3GB地址空间)。
Xen虚拟域的初始内存大小在该虚拟域启动时确定,各虚拟域以分区(Partition)形式共享整个系统的物理内存。
XenLinux实现了气球(Ballon)驱动程序来调节各虚拟域之间的物理内存共享。
当一个虚拟域ABalloon驱动程序需要更多的内存时,它可以通过气球驱动程序向Hypervisor提交内存请求,Hypervisor可以在未分配的内存中或向其他虚拟域B通过气球驱动程序回收内存,而提供该A。
虚拟域A的气球驱动程序在得到Hypervisor新提供的内存后向操作系统释放。
每一个x86客户机的内存地址总是从0开始的,而位于该地址的物理内存只有一块。
因此,监控程序必须把客户机从0开始的内存地址映像到其他物理内存页,必须把客户机虚拟地址(GuestVirtualAddress)到客户机物理地址(GUestPhysicalAddress)的映像(客户机页表)进行重新映像,即建立客户机虚拟地址到机器物理地址(MachinePhysicalAddress或PhysicalAddress)的映像。
Xen泛虚拟化(domainU)实现采用修改客户机页表的方式实现这一重新映像,硬件虚拟机(HVM)采用影像页表(ShadOWPageTable)方式实现。
图2[1]
方法一:
如图2所示,domainU使用的泛虚拟化方式是直接模式:
直接地由MMU记录guestOS的页表,并且限制住guestOS只能做读访问,页表更新通过hypercall
mmu_update(mmuupdatet*req,intcount,int*successcount,domidtdomid)
传递给Xen,更新请求在被采纳以前经过确认,这么做就确保了安全性。
给机器的每个页框都建立了一个相关的类型和引用数。
一个页框在任何时候都会排它地具有下述的某一个类型:
页目录(PD),页表(PT),局部描述符表(LDT),全局描述符表(GDT)。
同时,这个页框还可能是可写(RW)的类型。
一个guestOS可以为它自己所属的页框创建可读的映射,而不必理会页框的当前类型。
一个页框只有在它的引用数为0的时候,才能被安全地重新分配给其它任务使用。
这个机制被用于满足系统对安全性一贯的需求;
例如,一个domain不能够任意地对页表的某个部分建立可写的映射,如果要这么做的话就必须要求页框同时具有PT和RW类型。
为了减少所需的hypercall调用的次数,guestOS能够在利用一个hypercallmmu_update进行整批处理之前在局部将更新排入一个队列。
必须确保更新被提交得足够早以保证准确性。
一个guestOS在第一次使用一个新的映射前要执行一次TLB刷新:
这确保了任何被缓存的改变(这些改变目前仅存于TLB中,还没有被写入内存页表项)都是没有经过确认的。
因此,在TLB刷新之前立即提交那些待处理的更新就可以满足正确性(更新的时间,解决了什么时候进行成批更新操作以保证正确性的问题)。
可是,有一些guestOS会在TLB中没有陈旧表项存在的时候省略掉这个刷新的步骤。
在这种情况下,就有可能发生第一次试图使用新的映射时会发生缺页错误(因为没有刷新TLB)。
这时,guestOS错误操作程序就必须要核查是否需要更新;
如果需要的话,TLB就要被刷新(加入所缺的内容),并且将导致刚才缺页错误的指令重新执行。
更新页表操作执行时,必须调用vmassisthypercall,这时,所有对该页表项的连接都会被切断,直至做了从新connect和validate操作后MMU才能对其进行访问。
方法二:
图3[2]
影子页表的设计源自页表和TLB基本交互过程,是在不修改GuestOS上实现更加快速分页机制的核心。
其本质就是,整个ShadowPageTable可以看作是GuestOS页表的TLB,是GuestOS页表的缓存,影子页表是对GuestOS页表的仿真(并不一定完全一致)。
为什么要缓存GuestOS页表到影子页表呢?
在不修改GuestOS的前提下,用在创建HVMDomain时必须创建P2M和M2P表(P指Pseudo-physicaladdress,M指Machine-physicaladdress),其实在不修改GuestOS代码的前提下,GuestOS页表中查到的虚地址对应的实地址是Pseudo-physicaladdress,需要查P2M表转化成Machine-physicaladdress(真实物理地址)才能对内存进行访问。
但是如果每次都查P2M表就会大大损失效率。
有一个办法就是用VT技术截获更新页表指令,如图3,然后返回地址时返回的是ShadowPageTable里由GuestOS的虚地址查到的Machine-physicaladdress,这样变2次访问内存为1次,大大提高了效率(Vmware每次都查P2M表)。
为什么要缓存了GuestOS页表呢?
因为刚开始,ShadowPageTable什么也没有,刚开始都是缺页,然后都去查真实地址hashtable(为了提高效率,对P2M表建立了一个hash表,GuestOS页表中虚拟物理地址和该页表类型为hash键值,用来获取Machine-physicaladdress),查到后存到ShadowPageTable中,以后在访问直接从ShadowPageTable用GuestOS的虚地址去获取Machine-physicaladdress,根据程序的局部性原理大多数情况可以达到很高的命中率,所以查真实物理地址Machine-physicaladdress这一步的时间可以大大缩减。
如图4,就是这一过程的原理图,GuestOS中的AccessedBit标识页是否被访问(影子页表项索引是GuestOS虚地址,它在创建时就有与GuestOS中页表一一对应);
DirtyBit标识页是否被写入。
AccessedBit
DirtyBit
访问时相应操作
未访问未修改页表项,直接缺页,让影子页表去P2Mhashtable查到对应Machine-physicaladdress地址返回并保存在影子页表对应项中
1
未访问已修改页表项,直接缺页,让影子页表去P2Mhashtable查到对应Machine-physicaladdress地址返回并保存在影子页表中
已访问未修改页表项,直接访问影子页表返回Machine-physicaladdress
已访问已修改页表项,直接缺页,让影子页表去P2Mhashtable查到对应Machine-physicaladdress地址返回并从新填充该页表项
注意只有在第一次写入(DirtyBit未被设置时,才会清除R/Wbit,产生Writetrap,连续的写入只有一次trap。
操作系统可以执行INVLPG指令刷新TLB中的表项。
该指令以线性地址(虚地址)为参数,目的是刷新该线性地址在TLB中的物理地址使之无效。
为了进程间切换或其他目的,客户机会重写CR3(页目录寄存器),有时对CR3的重写意味刷新当前处理器TLB中的所有项目,使之无效,这正是通过INVLPG指令来实现。
图4[1]
方法一和方法二的最大区别在于GuestOS代码是否有所修改从而GuestOS是否是直接使用Machine-physicaladdress,方法一是直接使用的就是Machine-physicaladdress,需要修改GuestOS代码;
方法二是用捕获指令,间接查找shadowpagetable来获取Machine-physicaladdress,需要硬件支持。
从性能上来说方法一要优于方法二,但是方法二是不用修改GuestOS的代码的。
2.1.3设备I/O
从系统软件的角度来看,I/O设备其实就是特定资源的集合,即I/O端
口、内存映射I/O和中断等。
当操作系统按照硬件手册规定的方式操作资源,如读写I/O端口、接收中断,I/O设备就会按照规定完成的操作(读写磁盘,
收发网络包等)。
所以,进行I/O设备虚拟化一个很自然的想法就是捕获操作系统对这些网络设备资源的操作,然后根据硬件手册来模拟规定的操作。
这种完全虚拟化的方法优点和缺点一样明显。
因为是在平台层次上虚拟化,因此通用性很好,适用于所有的操作系统,只要操作系统有该虚拟设备的驱动程序,虚拟设备就能工作。
但这种方法引入了复杂的I/O操作路径,不仅需要在两个虚拟机之间切换,还需要多次的处理器运行模式的切换。
对于磁盘设备、网络设备等I/O访问非常频繁的设备来说,这个缺点尤其明显。
VBD/VNIF(虚拟块设备/虚拟网络接口)是泛虚拟化模型在Xen的实现,分别对应磁盘设备和网络设备。
如图1,VBD,VNIF由前端和后端组成。
前端是运行在普通Domain内核态的驱动程序,负责创建虚拟设备,并且转发虚拟设备的I/O请求。
后端是运行Domain0中的内核态的驱动程序,负责接收前端转发的I/O请求,并且通过真正的设备驱动来访问物理设备,完成I/O请求。
Hypervisor通过提供事件通道、授权表和环缓冲区来帮助前端与后端进行通信,注意这也是泛虚拟化比全虚拟化VMM快(如:
Vmware)的最重要原因之一。
VBD,VNIF简单、高效,但缺点是相对缺乏通用性,因为VBD,VNIF是与操作系统相关的,需要为每种操作系统实现前端(也即需要修改GuestOS代码)。
在Linux2.412.6内核和Windows下都有不同的前端实现。
后端只需要运行在Domain0中,因此只需要一种实现,目前是Linux2.6的内核驱动。
图5(仅以HVMDomain为例)
O端口访问处理
见图5,I/O端口访问处理过程如下:
1)I/O访问产生Hypercall请求(若是使用Intelvt技术会产生VMexit)。
2)解码指令
3)做I/O请求数据包(ioreq_t)描述event(前面已经讲过VMM要给Domain传消息通过异步Event通知)
4)发送event到Domain0的设备驱动模板(在XenLinux是一些drivermodule)
5)等待I/O端口和内存映射IO,IO的处理程序来自Domain0的设备模版
6)唤醒(解除阻塞)DomainU或是HVMDomain
7)通过异步Event(DomainU)或是VMResume即VMentry(HVMDomain)回到GuestOS。
I/O处理过程
1)在收到缺页中断导致VMexit或Hypercall时,检查PTE(页表项)是否在GuestOSpage-frame的内存IO的范围。
2)在相应的范围时,解码指令并发送I/O请求包到Domain0的设备
3.虚拟设备驱动(泛虚拟化模型)
图6用于VMM(Xen)和GuestOS通信的异步I/O环结构
Xen采用了事件通道(EventChanne1)、环缓冲区(RingBufer)、授权表(Grant
Table)和Xenstore机制,很好地解决了这个问题。
事件通道是类似于中断的一种机制,用于通知虚拟机对事件进行处理。
在VBD,VNIF中,当有请求等待处理或请求已经完成需要查收时,前端或后端使用事件通道通知对方。
环缓冲区顾名思义,就是环状的缓冲区,里面放置请求和应答。
如图6所示,环缓冲区
采用生产者消费者模式,甲方首先放置请求,乙方获取并处理完请求后,将处理结果再次放置回环缓冲区中,甲方最后获取处理结果。
甲和乙一次可
以放置和处理多个请求,只要环状缓冲区还有空间。
授权表是一种页面访问授
权机制。
通常情况下,DomainA和DomainB只能访问属于自己的页面。
但是,DomainA可以通过接权表操作,授权DomainB可以读,写DomainA的指定页面。
在经过DomainA的授权后,DomainB可以通过授权表的操作,真正对指定页面进行读写。
授权表主要用于前端与后端的大块数据传输。
VBD主要使用授权表来进行数据的读写。
VNIF还使用授权表来实现数据页的交换,实现网络包的零拷贝。
Xenstore本质上是一个小的树状数据库,类似于Windows中的注册表,主要用于存放各个虚拟机的配置数据。
数据库存放在Domain0,由一个守护进程负责对它的所有操作(读、写、事件触发等)。
VBD,VNIF就使用Xenstore来保存
和共享配置数据。
VBD,VNIF的前端和后端还大量使用Xenstore的事件触发功能来进行异步通信。
时间和定时器
Xen为每个guestOS提供了系统时间,虚拟时间和挂钟时间(wall-clocktime)等三个概念。
系统时间是VMM(虚拟机监视器)的时间,系统时间是以纳秒为单位给出的,是从机器引导起来开始计算的时间,它记录的是精确的处理器执行的时钟周期数,时钟周期能够被外部时钟源锁频(例如,通过NTP时间服务)。
虚拟时间是GuestOS时间,domain的虚拟时间只是在它运行的时候才会被记入(比如虚拟机block,虚拟时间会停止):
这主要供guestOS的调度器使用来确保guestOS上的应用进程能够正确地共享时间片。
挂钟时间是Domain0的时间,挂钟时间是一个偏移,用于加到当前的真