没有虚拟化基础的童鞋可先阅读Linux阅码场前几天刊发的《KVM最初的2小时——KVM从入门到放弃(修订版) 》入门。
Xen虚拟机系统所采用的半虚拟化技术通过软件方法实现了x86架构的虚拟化,解决了x86架构所固有的虚拟化缺陷,即敏感和特权指令无法被VMM所捕获的缺陷。
G.Popek和R.Goldberg在1974年发表的论文中提到,作为向上层VM提供底层硬件抽象的一层轻量级的软件,VMM必须满足以下3个条件:
1.等价性(Equivalence) :应用程序在VMM 上的虚拟机执行,应与物理硬件上的执行行为相同。
2.资源控制(Resource Control) :物理硬件由VMM全权控,VM 及VM上的应用程序不得直接访问硬件。
3.有效性(Efficiency) :在虚拟执行环境中应用程序的绝大多数指令能够在VMM不干预的情况下,直接在物理硬件上执行。
CPU的指令按照运行级别的不同,可以划分为两类:
1.特权指令 :只能在最高级别上运行,在低级别状态下执行会产生trap。例如:LIDT只能在系统模式下执行,在其他模式下都会产生trap,中止执行。
2.非特权指令 :可以在各个级别的状态下执行。
引入虚拟化后,Guest OS就不能运行在Ring 0上。因此,原本需要在最高级别下执行的指令就不能够直接执行,而是交由VMM处理执行。这部分指令称为敏感指令 。当执行这些指令时,理论上都要产生trap被VMM捕获执行。敏感指令:Guest OS中必须由VMM处理的指令,因为这些指令必须工作在0环。
敏感指令包括:
- 企图访问或修改虚拟机模式或机器状态的指令。
- 企图访问或修改敏感寄存器或存储单元,如时钟寄存器、中断寄存器等的指令。
- 企图访问存储保护系统或内存、地址分配系统的指令。
- 所有I/O指令。
根据Popek和Goldberg的理论,如果指令集支持虚拟化就必须满足所有的敏感指令都是特权指令 。这样,当Guest OS运行在非最高特权级时,执行任意特权指令都能产生trap。该条件保证了任何影响VMM或VM正确运行的指令在VM上执行时都能被VMM捕获并将控制权转移到VMM上,从而保证了虚拟机环境的等价性和资源可控制性,保证虚拟机正确运行。
但是,x86架构并不满足这个条件。由于有些敏感指令不属于特权指令,从而阻碍了指令的虚拟化。(x86不满足的原因:有些必须由VMM处理的0环指令,工作在1环也不会产生trap,即敏感指令包含非特权指令。但是敏感指令必须工作在0环,所以这些非特权指令也必须陷入。)x86结构上的这些不是特权指令的敏感指令,称为临界指令 (Critical Instructions)。这些临界指令在x86架构下有17个,主要包含敏感指令的两类:敏感寄存器指令和保护系统指令(上面的2,3类)。
敏感寄存器指令:SGDT,SIDT,SLDT ;SMSW; PUSHF,POPF;
保护系统指令:LAR,LSL,VERR,VERW;POP ;PUSH ;CALL,JMP,INT n,RET ;STR ;MOV;
1.SGDT,SIDT,SLDT
三个指令分别表示:存全局描述符表(store GDT),存中断描述符表(store IDT),存本地描述符表(store LDT)。其中,SGDT和SIDT是将寄存器的值保存到一个6字节的存储单元中,SLDT是存到一个16或32位的寄存器中或存储单元中。
使用如下:SGDT m; SIDT m;SLDT r/m16
三个指令只能被操作系统使用,但没有被设为特权指令,当处于低级别的客户操作系统执行它们时,能够直接获得寄存器的值。但是,由于在硬件平台上的寄存器都只有一个,因而位于不同虚拟机中的Guest OS所获得的值只有一个。这显然是不正确的,因此VMM会为每个虚拟机配备一套虚拟的GDTR,IDTR, LDTR,以便客户操作系统访问寄存器的操作被VMM捕获,并重新定向访问相应的虚拟寄存器。
2.SMSW
SMSW表示存机器状态字(store machine status word),即将机器状态字的值(CR0中低16位的值)保存到一个寄存器或存储单元中,设置该指令是为了向下兼容286处理器,而之后的处理器都使用MOV指令读取机器状态字的值。
使用如下: SMSW r/m16
同样,该指令没有被设定为特权指令。当Guest OS查询机器状态时,其得到的是实际物理寄存器的状态,即VMM的状态,并非是Guest OS的状态。所以也需要设置相应的虚拟寄存器CR0。
3.PUSHF,POPF
POPF(pop stack into EFLAGS register)和PUSHF(push EFLAGS register onto the stack)是一对相反的指令。PUSHF是将EFLAGS的寄存器的低16位压栈,并将栈指针减2。 POPF是从栈顶弹出一个字到EFLAGS的寄存器中,并将栈指针加2。对应的32位指令为:PUSHFD和POPFD。
4.LAR,LSL,VERR,VERW
四个指令分别表示:加载访问权限(load access rights byte),加载段界限(load segment limit),验证段可读(Verify a segment for reading)、验证段可写(verify a segment for writing)。其中,LAR指令是从指定的段描述符中加载访问权限到另一个寄存器,并设置EFLAGS寄存器中的ZF标志位;LSL指令从指定的段描述符中加载段界限到另一个寄存器中,并设置ZF标志位;VERR/VERW指令是在当前的特权级下验证指定的段是否可读/可写。若是,则设置ZF标志位。
使用如下:
LAR r16,r16/m16 LSL r16,r16/m16
VERR r/m16 VERW r/m16
需要访问段描述符,但是Guest OS不处于最高级别,访问不能正确被执行。与CPL相关。
5.POP
同样,与CPL相关。当POP到段寄存器时,需要比较CPL。由Guest OS运行在ring 3上引起。
6.PUSH
同POP类似,PUSH不能应用于CS,DS 段寄存器,也与CPL相关。
7.Call,JMP,Int n,Ret
8.STR
Store Task Register,将当前任务寄存器的段选择符存入通用寄存器或存储单元中,这个段选择符指向TSS段。
9.MOV
本文来源于:
https://blog.csdn.net/handw/article/details/5770554
(完)
"Linux阅码场"是专业的Linux及系统软件技术交流社区,Linux系统人才培养基地,企业和Linux人才的连接枢纽。
查看我们精华技术文章请移步:
Linux阅码场精华文章汇总
求职招聘请移步:
Linuxer: 连接企业和Linux人才的platform总线