0
点赞
收藏
分享

微信扫一扫

【Inside the Linux Virtualization:Principle and Implementation】(一)

书 名: 深度探索linu系统虚拟化:原理与实现
图书定价: 89元
作 者: 王柏生 谢广军a
出 版 社: 机械工业出版社
出版日期: 2020-10-13
ISBN 号: 9787111666066
开 本: 16开
页 数: 292
版 次: 1-1


文章目录

  • ​​1. CPU 虚拟化​​
  • ​​1.1 x86 架构 CPU 虚拟化​​
  • ​​1.1.1 陷入和模拟模型​​
  • ​​1.1.2 x86 架构虚拟化的障碍​​
  • ​​1.1.3 VMX​​
  • ​​1.1.4 VCPU 生命周期​​
  • ​​1.2 虚拟机切入和退出​​
  • ​​1.2.1 GCC 内联汇编​​
  • ​​1.2.2 虚拟机切入和退出及相关的上下文保存​​


1. CPU 虚拟化

1.1 x86 架构 CPU 虚拟化

Gerald J. Popek, Robert P. Goldberg. Formal requirements for virtualizable third generation architectures. 1974, 17(7):412-421.

虚拟化的三个条件

  • 等价性 :guest 和 host 基本没有区别。
  • 高效性 :guest 中的绝大部分指令无须 VMM 干预直接运行在 lCPU 上,虚拟机的非特权指令直接运行在处理器上,大部分指令无须 VMM 干预直接在处理器上运行。
  • 资源控制 :由 VMM 协调 host 的资源,不能由 guest 控制宿主机资源。

1.1.1 陷入和模拟模型

处理器有两种运行模式:系统模式和用户模式,对应的CPU 指令也分为特权指令和非特权指令。
虚拟机的用户程序和内核程序均运行在用户模式——特权级压缩。
当虚拟机执行特权指令时,因为此刻在用户模式下,故触发处理器异常。陷入 VMM 中,由 VMM 代理虚拟机完成系统资源的访问(模拟)。

1.1.2 x86 架构虚拟化的障碍

在非特权级执行这些敏感指令(修改系统资源,在不同模式下行为有不同表现的指令)时,CPU 会抛出异常,进入 VMM 的异常处理函数,从而实现了 VM 访问敏感资源的目的。

存在问题:x86 架构并不是所有的敏感指令都是特权指令,有些敏感指令在非特权模式下执行并不会抛异常。

解决方案:

  1. 半虚拟化:修改 Guest 代码。
  2. 静态翻译:运行前扫描整个可执行文件,对敏感指令进行翻译,形成一个新的文件。(有些指令只在运行时才会产生副作用)
  3. 动态翻译:运行时以代码块为单位动态地修改二进制代码。

1.1.3 VMX

每当操作系统内核切换进程时,都会切换 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。

【Inside the Linux Virtualization:Principle and Implementation】(一)_寄存器

VMX 支持的 CPU:

  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 介入处理。

在虚拟化场景下,同一个 lCPU 一人分饰多角,分时运行着 Host 以及 Guest,在不同模式间按需切换,因此,不同模式也需要保存自己的上下文。

VMX 设计了一个保存上下文的数据结构——VMCS。

【Inside the Linux Virtualization:Principle and Implementation】(一)_寄存器_02


VMCS 状态 切换由硬件自动完成 控制信息 Host> Host-State Area Guest> Guest-State Area VM Entry时 CPU 将宿主机状态保存到这里 VM Exit时 CPU 将虚拟机状态保存到这里 VM-exit information fields VM exit 退出原因 VM-execution control fields 控制虚拟机运行的行为

在创建 VCPU 时,KVM 模块将为每个 VCPU 申请一个 VMCS,每次 CPU 准备切入 Guest 模式时,将设置其 VMCS 指针指向即将切入的 Guest 对应的 VMCS 示例。

并不是所有的状态都由 CPU 自动保存与恢复,我们还要考虑效率,将一些状态的控制交由 VMM,由软件自行控制。

1.1.4 VCPU 生命周期

VMM 使用一个线程来表达 VCPU 这个实体。

  1. 在用户空间准备好后,VCPU 所在线程向内核中 KVM 模块发起一个​​ioctl​​ 请求——KVM_RUN,告知内核中的 KVM 模块,用户空间的操作已经完成,可以切入 Guest 模块运行 Guest。
  2. 在进入内核态后,KVM 模块将调用 CPU 提供的虚拟化指令切入 Guest 模式。如果是首次运行 Guest,则使用 VMLanuch 指令,否则使用 VMResume 指令。在这个切换过程中,首先,Host CPU 的状态被保存到 Host 状态区中,非 CPU 自动保存的状态由 KVM 负责保存,然后,加载存储在 VMCS 中的 Guest 的状态到物理 CPU,非 CPU 自动恢复的状态则由 KVM 负责恢复。
  3. 物理 CPU 切入 Guest 模式,运行 Guest 指令。当执行 Guest 指令遇到敏感指令时,CPU 将从 Guest 模式切回到 Host 模式的 ring 0,进入 Host 内核的 KVM 模块。在这个切换过程中,首先,Guest CPU 的状态会被保存到 VMCS 中存储 Guest 状态的区域,然后,加载存储在 VMCS 中的 Host 的状态到物理 CPU。
  4. 处于内核态的 KVM 模块从 VMCS 中读取虚拟机退出的原因,尝试在内核中处理。如果内核中可以处理,那么虚拟机就不再切换到 Host 模式的用户态了,处理完后,直接快速切回 Guest。(轻量级虚拟机退出)
  5. 如果内核态的 KVM 模块不能处理虚拟机退出,那么 VCPU 将再一次上下文切换,从 Host 的内核态切换到 Host 的用户态,由 VMM 的用户空间部分进行处理。VMM 用户空间处理完毕,再次发起切入 Guest 模式的命令。

1.2 虚拟机切入和退出

1.2.1 GCC 内联汇编

__asm__ (
"movl %%eax, %%ebx \n\t"
"movl %0, %%eax \n\t"

:[[asmSymbolicName]] constraint (cvariablename)
:[[asmSymbolicName]] constraint (cexpression)
:list of clobbered registers
);
  • clobber list:某些汇编指令执行后会有一些副作用,可能会隐形地影响某些寄存器或者内存的值,如果被影响的寄存器或者内存没有在输入、输出操作数中列出来的,那么需要将这些寄存器或者内存列入​​clobber list​​。通过这种方式,内联汇编告知 GCC,需要"照顾"好这些被影响的寄存器或者内存。
  • ​[[asmSymbolicName]] constraint (cvariablename)​​:[[别名]] 约束(C 变量名)
int val = 100, sum = 0;

asm ("movl %1, %%rax; \n\t"
"movl %c[append], %%rbx; \n\t"
"addl %%rbx, %%rax; \n\t"
"movl %%rax, %0; \n\t"

: "="(sum)
: (c)(val), [append]"i"(400)
: "rbx"
);
  • 第 3 行:使用使用两个 “%” 引用寄存器,%1 引用的是输入操作数 val,其中 c 表示使用 rcx 寄存器保存 val,也就是在执行这条汇编指令之前,首先将 val 的值赋值给 rcx 寄存器中,然后汇编指令再将 rcx 寄存器的值赋值到 rax 寄存器中。
  • 第 4 行:引用的 append 是第 2 个输入操作数的符号名字,因为这是一个立即数,所以这个变量前面使用了 c 修饰符。
  • %0:表示引用的输出操作数 sum,这是 C 代码中的变量,因为 sum 是只写的输出操作数,所以使用约束 “=”。

这段代码中使用到了 rbx 寄存器,而 rbx 寄存器没有出现在输出、输入操作数中,所以内联汇编需要把 rbx 寄存器列入 clobber list 中,告诉 GCC 汇编指令污染了 rbx 寄存器,如果有必要,则需要在执行内联汇编指令前自行保存 rbx 寄存器,执行内联汇编指令后再自行恢复 rbx 寄存器。

1.2.2 虚拟机切入和退出及相关的上下文保存


举报

相关推荐

0 条评论