在上期,N因为满脑子都是风尘小姐姐,被T姐带着姑娘们修理了一顿,抱着电脑逃出了海淀大街38号。
小E受到N抱着电脑逃跑的启发,发现虚拟机的迁移,实际上只需要解决三个问题:
1. 运行时上下文热迁移:把虚拟机的vCPU内部各寄存器迁移到另一台宿主机为虚拟机分配的vCPU中:
2. RAM热迁移:把虚拟机的RAM内容复制到另一台宿主机为虚拟机分配的RAM中;
3. 持久化存储盘迁移:让迁移到新宿主机上的虚拟机挂载的系统盘和数据盘的数量与内容跟原来一致;
我们先解决最难的问题1吧!
我们在《虚拟化与云计算技术硬核内幕 (20) —— 时间管理大师(下)》提到过,在宿主机上看来,每个虚拟机是一个QEMU进程,虚拟机的vCPU是QEMU的线程。
因此,对虚拟机进行“乾坤大挪移”,实际上是将QEMU进程及进程中的各个线程进行搬移,将其在当前宿主机上中断,将运行状态复制到目标宿主机上重新拉起。
运行状态中,最重要的就是CPU内部的寄存器,最难迁移的也是这些。
只要翻开厚厚的《查泰莱夫人的情人》《Intel 64 and IA32 Architectures Software Developers Manual》,在第1卷(Basic Architecture)第3章(BASIC EXECUTION ENVIRONMENT)中,提到了,x86处理器的64位模式下,有这些寄存器:
- 通用寄存器 (General Purpose Registers):RAX, RBX, RCX, RDX, RDI, RSI, RBP, RSP, R8 - R15。这些寄存器会用于一般的计算,以及函数参数的传递;
- 段寄存器 (Segment Registers):CS, DS, ES, SS, FS, 和GS。这些段寄存器用于内存寻址,在保护模式下又被称为“段选择子”;
- 标志寄存器RFLAGS
- RIP 指令指针,这是最重要的寄存器,指向当前执行的指令。
在前面,我们提到过,当系统硬件的时钟中断到来时,操作系统提供的中断处理程序会将这些现场寄存器进行保存,特别地,RIP会被硬件自动保存到堆栈的栈顶处。
那么,在虚拟机需要迁移的时候,我们实际上需要做的就是,将虚拟机对应的QEMU进程中,各个线程的这一系列寄存器信息复制到目标宿主机,然后从目标虚拟机上再恢复这些信息到CPU。
那么,这些信息存在哪里呢?
原来,在操作系统中,每个线程都有自己的栈,如下图所示:
在线程正常运行态,每个栈分为若干Stack Frame,每个Stack Frame的栈底是上一个调用者的RIP,紧接着是函数入参 (如果参数大于6个),以及保存函数内部使用的局部变量。
这个图表现得很清楚了。
除了函数调用时,进行操作系统内核调用 (syscall等),以及中断或其他异常处理程序,也会将以RCS/RIP寄存器、RFlags标志寄存器为代表的关键寄存器保存到栈中,并视情况将RAX,RBX、RDS等通用寄存器保存到栈中。
那么,当我们需要对虚拟机进行“乾坤大挪移”的时候,首先就需要将栈中保存的这些寄存器信息(一般称为Register File,寄存器列)迁移到目的宿主机的栈中。
在这张图中就可以很清楚地看到了。当虚拟机需要从Host A迁移到Host B的时候,Hypervisor会向这个虚拟机对应的QEMU进程内,所有线程所运行的CPU发送核间中断。在核间中断处理程序中,各个线程会将自己的所有寄存器信息(Register File)保存到自己的栈中。然后,Hypervisor将这些栈中的Register File,以及栈中其他所有的Frame,通过网络发送到目标宿主机Host B。
目标宿主机Host B拿到这些宝贵的数据的时候,也会在内存中开辟栈区域,并将这些宝贵的Register File保存到栈顶,就可以利用pop指令从栈中恢复现场,并执行iret指令,把栈中保存的RIP恢复回CPU的寄存器中,开始执行QEMU的线程,也就是恢复虚拟机运行了。
这一切是多么神奇,就像图中空乘小姐姐吹了一口气,飞机就起飞了那样——
但,我们知道,空乘小姐姐并不可能真的吹一口气就让飞机起飞,所以,如果我们按上面的流程进行虚拟机迁移,也是不可能成功的。Host B上的CPU在执行iret指令后,会立即抛出异常,停止运行。
这是因为,我们只解决了第一个问题——保存CPU内部寄存器的运行状态,没有解决第二个问题——把内存内容拷贝到目的宿主机。
在下一期,我们将介绍第二个问题的解决,让空乘小姐姐真正实现“乾坤大挪移”。