作者 | 王柏生、谢广军
导读:本文摘自于王柏生、谢广军撰写的《深度探究Linux体系假造化:原理与完成》一书,先容了CPU假造化的基本看法,探究了x86架构在假造化时面临的停滞,以及为支持CPU假造化,Intel在硬件层面完成的扩展VMX。
同时,先容了在VMX扩展支持下,假造CPU从Host形式到Guest形式,再回到Host形式的完备生命周期。
Gerald J. Popek和Robert P. Goldberg在1974年公布的论文“Formal Requirements for Virtualizable Third Generation Architectures”中提出了假造化的3个条件:
1)等价性,即VMM必要在宿主机上为假造机模仿出一个实质上与物理机一律的情况。假造机在这个情况上运转与其在物理机上运转别无二致,除了约莫由于资源竞争大概VMM的干涉招致在假造情况中体现略有差别,好比假造机的I/O、网络等因宿主机的限速大概多个假造机共享资源,招致速率约莫要比独占物理机时慢一些。
2)高效性,即假造机指令实行的功能与其在物理机上运转比拟并无分明斲丧。该标准要求假造机中的绝大局部指令无须VMM干涉而直接运转在物理CPU上,好比我们在x86架构上经过Qemu运转的ARM体系并不是假造化,而是模仿。
3)资源控制,即VMM可以完全控制体系资源。由VMM控制和谐宿主机资源给各个假造机,而不克不及由假造机控制了宿主机的资源。
堕入和模仿模子
为了满意Gerald J. Popek和Robert P. Goldberg提出的假造化的3个条件,一个典范的处理方案是堕入和模仿(Trap and Emulate)模子。
寻常来说,处理器分为两种运转形式:体系形式和用户形式。相应地,CPU的指令也分为特权指令和非特权指令。
特权指令只能在体系形式运转,假如在用户形式运转就将触发处理器特别。利用体系允许内核运转在体系形式,由于内核必要办理体系资源,必要运转特权指令,而平凡的用户步骤则运转在用户形式。
在堕入和模仿模子下,假造机的用户步骤仍旧运转在用户形式,但是假造机的内核也将运转在用户形式,这种办法称为特权级紧缩(Ring Compression)。在这种办法下,假造机中的非特权指令直接运转在处理器上,满意了假造化标准中高效的要求,即大局部指令无须VMM干涉直接在处理器上运转。
但是,当假造机实行特权指令时,由于是在用户形式下运转,将触发处理器特别,从而堕入VMM中,由VMM署理假造机完成体系资源的拜候,即所谓的模仿(emulate)。
云云,又满意了假造化标准中VMM控制体系资源的要求,假造机将不会由于可以直接运转特权指令而修正宿主机的资源,从而毁坏宿主机的情况。
x86架构假造化的停滞
Gerald J. Popek和Robert P. Goldberg指出,修正体系资源的,大概在不同形式下举动有不同体现的,都属于敏感指令。
在假造化场景下,VMM必要监测这些敏感指令。一个支持假造化的体系架构的敏感指令都属于特权指令,即在非特权级别实行这些敏感指令时CPU会抛出特别,进入VMM的特别处理函数,从而完成了控制VM拜候敏感资源的目标。
但是,x86架构恰好不克不及满意这个准则。x86架构并不是一切的敏感指令都是特权指令,有些敏感指令在非特权形式下实行时并不会抛出特别,此时VMM就无法拦阻处理VM的举动了。
我们以修正FLAGS存放器中的IF(Interrupt Flag)为例,我们起首使用指令pushf将FLAGS存放器的内容压到栈中,然后将栈顶的IF清零,最初使用popf指令从栈中规复FLAGS存放器。假如假造机内核没有运转在ring 0,x86的CPU并不会抛出特别,而只是静静地忽略指令popf,因此假造布局闭IF的目标并没有奏效。
有人提出半假造化的处理方案,即修正Guest的代码,但是这不切合假造化的纯透准则。厥后,人们提出了二进制翻译的方案,包含静态翻译和动态翻译。静态翻译就是在运转前扫描整个可实行文件,对敏感指令举行翻译,构成一个新的文件。
但是,静态翻译必需事先处理,并且关于有些指令仅有在运转时才会产生的反作用,无法静态处理。于是,动态翻译应运而生,即在运转时以代码块为单位动态地修正二进制代码。动态翻译在很多VMM中取得使用,并且优化的后果十分不错。
VMX
固然各位从软件层面接纳了多种方案来处理x86架构在假造化时碰到的成绩,但是这些处理方案除了引入了分外的开支外,还给VMM的完成带来了宏大的繁复性。于是,Intel实验从硬件层面处理这个成绩。
Intel并没有将那些非特权的敏感指令修正为特权指令,由于并不是一切的特权指令都必要拦阻处理。举一个典范的例子,每当利用体系内核切换历程时,都市切换cr3存放器,使其指向如今运转历程的页表。
但是,当使用影子页表举行GVA到HPA的映射时,VMM模块必要捕捉Guest每一次设置cr3存放器的利用,使其指向影子页表。而当启用了硬件层面的EPT支持后,cr3存放器不再必要指向影子页表,其仍旧指向Guest的历程的页表。
因此,VMM无须再捕捉Guest设置cr3存放器的利用,也就是说,固然写cr3存放器是一个特权益用,但这个利用不必要堕入VMM。
Intel开发了VT武艺以支持假造化,为CPU增长了Virtual-Machine Extensions,简称VMX。一旦启动了CPU的VMX支持,CPU将提供两种运转形式:VMX Root Mode和VMX non-Root Mode,每一种形式都支持ring 0 ~ ring 3。
VMM运转在VMX Root Mode,除了支持VMX外,VMX Root Mode和平凡的形式并无实质区别。VM运转在VMX non-Root Mode,Guest无须再接纳特权级紧缩办法,Guest kernel可以直接运转在VMX non-Root Mode的ring 0中,如图1所示。
图1 VMX运转形式
处于VMX Root Mode的VMM可以经过实行CPU提供的假造化指令VMLaunch切换到VMX non-Root Mode,由于这个历程相当于进入Guest,以是通常也被称为VM entry。
当Guest内里实行了敏感指令,好比某些I/O利用后,将触发CPU产生堕入的举措,从VMX non-Root Mode切换回VMX Root Mode,这个历程相当于退去VM,以是也称为VM exit。
然后VMM将对Guest 的利用举行模仿。比拟于将Guest的内核也运转在用户形式(ring 1 ~ ring 3)的办法,支持VMX的CPU有以下3点不同:
1)运转于Guest形式时,Guest用户空间的体系调用直接堕入Guest形式的内核空间,而不再是堕入Host形式的内核空间。
2)关于外部中缀,由于必要由VMM控制体系的资源,以是处于Guest形式的CPU收到外部中缀后,则触发CPU从Guest形式退去到Host形式,由Host内核处理外部中缀。
处理完中缀后,再重新切入Guest形式。为了提高I/O听从,Intel支持外设透传形式,在这种形式下,Guest不必产生VM exit,“装备假造化”一章将讨论这种特别办法。
3)不再是一切的特权指令都市招致处于Guest形式的CPU产生VM exit,仅当运转敏感指令时才会招致CPU从Guest形式堕入Host形式,由于有的特权指令并不必要由VMM到场处理。
好像一个CPU可以分时运转多个职责一样,每个职责有本人的上下文,由调治器在调治时切换上下文,从而完成同一个CPU同时运转多个职责。在假造化场景下,同一个物理CPU“一人分饰多角”,分时运转着Host及Guest,在不同形式间按需切换,因此,不同形式也必要保存本人的上下文。
为此,VMX计划了一个保存上下文的数据布局:VMCS。每一个Guest都有一个VMCS实例,当物理CPU加载了不同的VMCS时,将运转不同的Guest如图2所示。
图2 多个Guest切换
VMCS中主要保存着两大类数据,一类是形态,包含Host的形态和Guest的形态,别的一类是控制Guest运转时的举动。此中:
1)Guest-state area,保存假造机形态的地区。当产生VM exit时,Guest的形态将保存在这个地区;当VM entry时,这些形态将被装载到CPU中。这些都是硬件层面的主动举动,无须VMM编码干涉。
2)Host-state area,保存宿主机形态的地区。当产生VM entry时,CPU主动将宿主机形态保存到这个地区;当产生VM exit时,CPU主动从VMCS规复宿主机形态到物理CPU。
3)VM-exit information fields。当假造机产生VM exit时,VMM必要晓得招致VM exit的缘故,然后才干“对症下药”,举行相应的模仿利用。为此,CPU会主动将Guest退去的缘故保存在这个地区,供VMM使用。
4)VM-execution control fields。这个地区中的种种字段控制着假造机运转时的一些举动,好比设置Guest运转时拜候cr3存放器时对否触发VM exit;控制VM entry与VM exit时举动的VM-entry control fields和VM-exit control fields。别的另有很多不同功效的地区,我们不再逐一摆列,读者如有必要可以查阅Intel手册。
在创建VCPU时,KVM模块将为每个VCPU哀求一个VMCS,每次CPU准备切入Guest形式时,将设置其VMCS指针指向即将切入的Guest对应的VMCS实例:
commit 6aa8b732ca01c3d7a54e93f4d701b8aabbe60fb7
[PATCH] kvm: userspace interface
linux.git/drivers/kvm/vmx.c
static struct kvm_vcpu *vmx_vcpu_load(struct kvm_vcpu *vcpu)
{
u64 phys_addr = __pa(vcpu->vmcs);
int cpu;
cpu = get_cpu;
…
if (per_cpu(current_vmcs, cpu) != vcpu->vmcs) {
…
per_cpu(current_vmcs, cpu) = vcpu->vmcs;
asm volatile (ASM_VMX_VMPTRLD_RAX "; setna %0"
: "=g"(error) : "a"(&phys_addr), "m"(phys_addr)
: "cc");
…
}
…
}
并不是一切的形态都由CPU主动保存与规复,我们还必要思索听从。以cr2存放器为例,大大多时分,从Guest退去Host到再次进入Guest时期,Host并不会改动cr2存放器的值,并且写cr2的开支很大,假如每次VM entry时都更新一次cr2,除了糜费CPU的算力毫偶然义。因此,将这些形态交给VMM,由软件自行控制更为公道。
VCPU生命周期
关于每个假造处理器(VCPU),VMM使用一个线程来代表VCPU这个实体。在Guest运转历程中,每个VCPU基本都在如图3所示的形态中不休地转换。
图3 VCPU生命周期
1)在用户空间准备好后,VCPU地点线程向内核中KVM模块倡导一个ioctl哀求KVM_RUN,见告内核中的KVM模块,用户空间的利用以前完成,可以切入Guest形式运转Guest了。
2)在进入内核态后,KVM模块将调用CPU提供的假造化指令切入Guest形式。假如是初次运转Guest,则使用VMLaunch指令,不然使用VMResume指令。在这个切换历程中,起首,CPU的形态(也就是Host的形态)将会被保存到VMCS中存储Host形态的地区,非CPU主动保存的形态由KVM卖力保存。然后,加载存储在VMCS中的Guest的形态到物理CPU,非CPU主动规复的形态则由KVM卖力规复。
3)物理CPU切入Guest形式,运转Guest指令。当实行Guest指令碰到敏感指令时,CPU将从Guest形式切回到Host形式的ring 0,进入Host内核的KVM模块。在这个切换历程中,起首,CPU的形态(也就是Guest的形态)将会被保存到VMCS中存储Guest形态的地区,然后,加载存储在VMCS中的Host的形态到物理CPU。相反的,非CPU主动保存的形态由KVM模块卖力保存。
4)处于内核态的KVM模块从VMCS中读取假造机退去缘故,实验在内核中处理。假如内核中可以处理,那么假造机就不必再切换到Host形式的用户态了,处理完后,直接快速切回Guest。这种退去也称为轻量级假造机退去。
5)假如内核态的KVM模块不克不及处理假造机退去,那么VCPU将再举行一次上下文切换,从Host的内核态切换到Host的用户态,由VMM的用户空间局部举行处理。VMM用户空间处理终了,再次倡导切入Guest形式的指令。在整个假造机运转历程中,步调1~5循环往复。
底下是KVM切入、切出Guest的代码:
commit 6aa8b732ca01c3d7a54e93f4d701b8aabbe60fb7
[PATCH] kvm: userspace interface
linux.git/drivers/kvm/vmx.c
static int vmx_vcpu_run(struct kvm_vcpu *vcpu, …)
{
u8 fail;
u16 fs_sel, gs_sel, ldt_sel;
int fs_gs_ldt_reload_needed;
again:
…
/* Enter guest mode */
"jne launched \n\t"
ASM_VMX_VMLAUNCH "\n\t"
"jmp kvm_vmx_return \n\t"
"launched: " ASM_VMX_VMRESUME "\n\t"
".globl kvm_vmx_return \n\t"
"kvm_vmx_return: "
/* Save guest registers, load host registers, keep flags */
…
if (kvm_handle_exit(kvm_run, vcpu)) {
…
goto again;
}
}
return 0;
}
在从Guest退去时,KVM模块起首调用函数kvm_handle_exit实验在内核空间处理Guest退去。函数kvm_handle_exit有个商定,假如在内核空间可以告捷处理假造机退去,大概是由于别的干扰好比外部中缀招致假造机退去等无须切换到Host的用户空间,则前往1;
不然前往0,表现必要告急KVM的用户空间处理假造机退去,好比必要KVM用户空间的模仿装备处理外设哀求。
假如内核空间告捷处理了假造机的退去,则函数kvm_handle_exit前往1,在上述代码中即直接跳转到标签again处,然后步骤流程会再次切入Guest。
假如函数kvm_handle_exit前往0,则函数vmx_vcpu_run完毕实行,CPU从内核空间前往到用户空间,以kvmtool为例,其干系代码片断如下:
commit 8d20223edc81c6b199842b36fcd5b0aa1b8d3456
Dump KVM_EXIT_IO details
kvmtool.git/kvm.c
int main(int argc, char *argv[])
{
…
for (;;) {
kvm__run(kvm);
switch (kvm->kvm_run->exit_reason) {
case KVM_EXIT_IO:
…
}
…
}
依据代码可见,kvmtool倡导进入Guest的代码处于一个for的无穷循环中。当从KVM内核空间前往用户空间后,kvmtool在用户空间处理Guest的哀求,好比调用模仿装备处理I/O哀求。
在处理完Guest的哀求后,重新进入下一轮for循环,kvmtool再次哀求KVM模块切入Guest。
福利
CSDN 携手【机器产业出书社】送出
《深度探究Linux体系假造化:原理与完成》一本
停止11月25日18:00点
作者简介:
王柏生
资深武艺专家,先后就职于中科院软件所、红旗Linux和百度,现任百度主任架构师。在利用体系、假造化武艺、分布式体系、云盘算、主动驾驶等干系范畴耕作多年,有着丰厚的实践履历。
著有热销书《深度探究Linux利用体系》(2013年出书)。
谢广军
盘算机专业博士,毕业于南开大学盘算机系。
资深武艺专家,有多年的IT行业事情履历。现承继百度智能云副总司理,卖力云盘算干系产物的研发。多年来不休从事利用体系、假造化武艺、分布式体系、大数据、云盘算等干系范畴的研发事情,实践履历丰厚。
版权声明:本文来自互联网整理发布,如有侵权,联系删除
原文链接:https://www.yigezhs.comhttps://www.yigezhs.com/qingganjiaoliu/34326.html