休眠的客户虚拟机¶
背景¶
Linux 支持自身休眠以节省电量的功能。休眠有时被称为“挂起到磁盘”,因为它会将内存映像写入磁盘,并将硬件置于最低的功耗状态。从休眠状态恢复时,硬件会重新启动,内存映像会从磁盘恢复,以便系统可以从中断处继续执行。请参阅《系统休眠状态》中的“休眠”部分。
休眠通常在单用户设备上进行,例如个人笔记本电脑。例如,笔记本电脑在合上盖子时进入休眠状态,在再次打开盖子时恢复。休眠和恢复发生在相同的硬件上,负责协调休眠步骤的 Linux 内核代码假定在休眠状态下硬件配置不会改变。
可以在 Linux 中通过向 /sys/power/state 写入“disk”或调用带有适当参数的 reboot 系统调用来启动休眠。此功能可能由用户空间命令(例如“systemctl hibernate”)封装,这些命令可以直接从命令行运行,或响应诸如笔记本电脑盖子关闭等事件。
客户虚拟机休眠的注意事项¶
Hyper-V 上的 Linux 客户机也可以休眠,在这种情况下,硬件是 Hyper-V 为客户虚拟机提供的虚拟硬件。只有目标客户虚拟机进入休眠,而其他客户虚拟机和底层 Hyper-V 主机则继续正常运行。虽然底层运行的 Windows Hyper-V 和物理硬件也可以使用 Windows 主机中的休眠功能进行休眠,但主机休眠及其对客户虚拟机的影响不属于本文档的范围。
恢复一个休眠的客户虚拟机比恢复物理硬件更具挑战性,因为虚拟机使得在休眠和恢复之间更改硬件配置变得非常容易。即使在休眠的同一虚拟机上进行恢复,内存大小也可能已更改,或者可能已添加或移除虚拟网卡或 SCSI 控制器。分配给虚拟机的虚拟 PCI 设备可能已添加或移除。大多数此类更改会导致恢复步骤失败,尽管添加新的虚拟网卡、SCSI 控制器或 vPCI 设备应该可行。
额外复杂性可能随之而来,因为休眠的虚拟机的磁盘可以移动到另一个新创建的、具有相同虚拟硬件配置的虚拟机上。虽然希望在此类移动后能成功从休眠中恢复,但仍存在挑战。有关此场景及其限制的详细信息,请参阅下面的“在不同虚拟机上恢复”部分。
Hyper-V 还提供了将虚拟机从一个 Hyper-V 主机移动到另一个主机的方法。Hyper-V 尝试使用 VM 配置版本来确保处理器型号和 Hyper-V 版本的兼容性,并阻止移动到不兼容的主机。Linux 通过在启动时检测主机和处理器差异来适应这些差异,但在休眠映像中恢复执行时不会进行此类检测。如果虚拟机在一个主机上休眠,然后在处理器型号或 Hyper-V 版本不同的主机上恢复,则休眠映像中记录的设置可能与新主机不匹配。由于 Linux 在恢复休眠映像时不会检测此类不匹配,因此可能导致未定义行为和失败。
启用客户虚拟机休眠¶
Hyper-V 客户虚拟机休眠默认是禁用的,因为休眠与 Hyper-V 气球驱动程序提供的内存热添加功能不兼容。如果使用了热添加功能且虚拟机休眠,则它会以比启动时更多的内存量休眠。但当虚拟机从休眠中恢复时,Hyper-V 只会向虚拟机分配最初分配的内存,内存大小不匹配会导致恢复失败。
要启用 Hyper-V 虚拟机进行休眠,Hyper-V 管理员必须在 Hyper-V 为客户虚拟机提供的 ACPI 配置中启用 ACPI 虚拟 S4 睡眠状态。此启用操作通过修改虚拟机的 WMI 属性来完成,具体步骤超出了本文档的范围,但可在网上查阅。此启用被视为管理员优先考虑虚拟机中的 Linux 休眠而非热添加的指示,因此 Linux 中的 Hyper-V 气球驱动程序会禁用热添加。如果 /sys/power/disk 的内容包含“platform”作为选项,则表示已启用。此启用在 /sys/bus/vmbus/hibernation 中也可见。请参阅函数 hv_is_hibernation_supported()。
Linux 在 x86 上支持 ACPI 睡眠状态,但在 arm64 上不支持。
因此,在 Hyper-V 上,arm64 不支持 Linux 客户虚拟机休眠。¶
客户虚拟机可以使用标准 Linux 方法自行启动休眠,即向 /sys/power/state 写入“disk”或使用 reboot 系统调用。作为额外的一层,Hyper-V 上的 Linux 客户机支持“关机”集成服务,通过该服务,Hyper-V 管理员可以使用虚拟机外部的命令来指示 Linux 虚拟机休眠。该命令会向 Linux 中的 Hyper-V 关机驱动程序生成一个请求,该驱动程序会发送 uevent “EVENT=hibernate”。请参阅内核函数 shutdown_onchannelcallback() 和 send_hibernate_uevent()。虚拟机中必须提供一个 udev 规则来处理此事件并启动休眠。
休眠和恢复期间处理 VMBus 设备¶
VMBus 总线驱动程序以及各个 VMBus 设备驱动程序都实现了挂起和恢复功能,这些功能作为 Linux 协调休眠和从休眠恢复过程的一部分被调用。总体方法是保留主 VMBus 通道及其相关 Linux 设备(例如 SCSI 控制器等)的数据结构,以便它们被捕获到休眠映像中。
这种方法允许与设备相关的任何状态在休眠/恢复过程中持久化。当虚拟机恢复时,Hyper-V 会重新提供设备,并将它们连接到已恢复的休眠映像中已存在的数据结构。
VMBus 设备通过类别和实例 GUID 标识。(请参阅《VMBus》文档中“VMBus 设备创建/删除”一节。)从休眠中恢复时,恢复功能预期 Hyper-V 提供的设备具有与休眠时存在的设备相同的类别/实例 GUID。拥有相同的类别/实例 GUID 允许将提供的设备与现在已恢复的休眠映像内存中的主 VMBus 通道数据结构进行匹配。如果提供的任何设备与已存在的主 VMBus 通道数据结构不匹配,它们将作为新添加的设备正常处理。如果恢复的休眠映像中存在的主 VMBus 通道与恢复的虚拟机中提供的设备不匹配,恢复序列会等待 10 秒,然后继续进行。但未匹配的设备很可能导致恢复的虚拟机中出现错误。
恢复现有主 VMBus 通道时,新提供的 relid 可能不同,因为 relid 每次虚拟机启动时都可能改变,即使虚拟机配置未发生变化。VMBus 总线驱动程序的恢复功能会匹配类别/实例 GUID,并在 relid 发生变化时进行更新。
VMBus 子通道不会持久化到休眠映像中。每个 VMBus 设备驱动程序的挂起功能必须在休眠前关闭所有子通道。关闭子通道会导致 Hyper-V 发送 RESCIND_CHANNELOFFER 消息,Linux 会通过释放通道数据结构来处理此消息,从而移除子通道的所有痕迹。相比之下,主通道被标记为关闭,其环形缓冲区被释放,但 Hyper-V 不发送撤销消息,因此通道数据结构继续存在。恢复时,设备驱动程序的恢复功能会重新分配环形缓冲区并重新打开现有通道。然后它会与 Hyper-V 通信,从头开始重新打开子通道。
Hyper-V 套接字的 Linux 端在休眠时被强制关闭。客户机无法强制关闭套接字的主机端,但主机端上的任何主机侧操作都将产生错误。
VMBus 设备在“冻结”和“断电”阶段使用相同的挂起功能,在“解冻”和“恢复”阶段使用相同的恢复功能。有关这些阶段的顺序,请参阅《设备电源管理基础》中的“进入休眠”部分。
详细休眠序列¶
Linux 电源管理 (PM) 子系统通过冻结用户空间进程并分配内存以容纳休眠映像来准备休眠。
作为“冻结”阶段的一部分,Linux PM 依次调用每个 VMBus 设备的“挂起”功能。如上所述,此功能会移除子通道,并将主通道置于关闭状态。
Linux PM 调用 VMBus 总线的“挂起”功能,该功能会关闭所有 Hyper-V 套接字通道并卸载与 Hyper-V 主机的顶级 VMBus 连接。
Linux PM 禁用非启动 CPU,在之前分配的内存中创建休眠映像,然后重新启用非启动 CPU。休眠映像包含已关闭主通道的内存数据结构,但不包含子通道。
作为“解冻”阶段的一部分,Linux PM 调用 VMBus 总线的“恢复”功能,该功能会重新建立顶级 VMBus 连接,并请求 Hyper-V 重新提供 VMBus 设备。当主通道收到提供时,relid 会按前述方式更新。
Linux PM 调用每个 VMBus 设备的“恢复”功能。每个设备重新打开其主通道,并酌情与 Hyper-V 通信以重新建立子通道。子通道被重新创建为新通道,因为它们之前已在步骤 2 中完全移除。
VMBus 设备现在再次工作,Linux PM 将休眠映像从内存写入磁盘。
Linux PM 重复上述步骤 2 和 3,作为“断电”阶段的一部分。VMBus 通道关闭,顶级 VMBus 连接被卸载。
Linux PM 禁用非启动 CPU,然后进入 ACPI 睡眠状态 S4。休眠现已完成。
详细恢复序列¶
客户虚拟机启动一个新的 Linux 操作系统实例。在启动过程中,会建立顶级 VMBus 连接,并启用合成设备。这通过不涉及休眠的正常路径发生。
Linux PM 休眠代码读取交换空间,以查找并将休眠映像读入内存。如果没有休眠映像,则此次启动将成为正常启动。
如果这是从休眠中恢复,则“冻结”阶段用于关闭运行中的新操作系统实例中的 VMBus 设备并卸载顶级 VMBus 连接,就像休眠序列中的步骤 2 和 3 一样。
Linux PM 禁用非启动 CPU,并将控制权转移到已读入的休眠映像。在当前运行的休眠映像中,非启动 CPU 会重新启动。
作为“恢复”阶段的一部分,Linux PM 重复休眠序列中的步骤 5 和 6。重新建立顶级 VMBus 连接,并接收提供并与映像中的主通道进行匹配。Relid 被更新。VMBus 设备恢复功能重新打开主通道并重新创建子通道。
Linux PM 退出休眠恢复序列,虚拟机现在从休眠映像正常运行。
键值对 (KVP) 伪设备异常¶
VMBus KVP 设备的行为与 Hyper-V 提供的其他伪设备不同。当 KVP 主通道关闭时,Hyper-V 会发送一个撤销消息,导致设备的所有痕迹被移除。但 Hyper-V 随后会重新提供该设备,导致其被重新创建。移除和重新创建发生在休眠的“冻结”阶段,因此休眠映像包含重新创建的 KVP 设备。在恢复序列的“冻结”阶段,当仍在新操作系统实例中时,也会发生类似的行为。但在两种情况下,顶级 VMBus 连接随后都会被卸载,这会导致设备在 Hyper-V 端被丢弃。因此没有造成损害,一切仍然正常工作。
虚拟 PCI 设备¶
虚拟 PCI 设备是物理 PCI 设备,它们直接映射到虚拟机的物理地址空间中,以便虚拟机可以直接与硬件交互。vPCI 设备包括通过 Hyper-V 称为“独立设备分配”(DDA) 访问的设备,以及 SR-IOV 网卡虚拟功能 (VF) 设备。请参阅《PCI 直通设备》。
Hyper-V DDA 设备在建立顶级 VMBus 连接后提供给客户虚拟机,就像 VMBus 合成设备一样。它们被静态分配给虚拟机,并且其实例 GUID 不会改变,除非 Hyper-V 管理员更改了配置。DDA 设备在 Linux 中表示为虚拟 PCI 设备,它们既有 VMBus 标识,也有 PCI 标识。因此,Linux 客户机休眠首先将 DDA 设备作为 VMBus 设备处理,以管理 VMBus 通道。但随后它们也作为 PCI 设备处理,使用其原生 PCI 驱动程序实现的休眠功能。
SR-IOV 网卡 VF 也具有 VMBus 标识和 PCI 标识,总体处理方式与 DDA 设备类似。一个区别是,在虚拟机首次启动时,VF 不会提供给虚拟机。相反,VMBus 合成网卡驱动程序首先开始运行并告知 Hyper-V 已准备好接受 VF,然后才提供 VF。然而,VMBus 连接可能稍后被卸载,然后在不重启虚拟机的情况下重新建立,如上文《详细休眠序列》中的步骤 3 和 5 以及《详细恢复序列》中所示。在这种情况下,VF 很可能在初始启动时就成为了虚拟机的一部分,因此当 VMBus 连接重新建立时,VF 会在重新建立的连接上提供,无需合成网卡驱动程序介入。
UIO 设备¶
VMBus 设备可以使用 Hyper-V UIO 驱动程序 (uio_hv_generic.c) 暴露给用户空间,以便用户空间驱动程序可以控制和操作该设备。然而,VMBus UIO 驱动程序不支持休眠所需的挂起和恢复操作。如果 VMBus 设备配置为使用 UIO 驱动程序,则虚拟机休眠会失败,Linux 继续正常运行。Hyper-V UIO 驱动程序最常见的用途是 DPDK 网络,但也有其他用途。
在不同虚拟机上恢复¶
此场景发生在 Azure 公有云中,休眠的客户虚拟机仅以保存的配置和磁盘形式存在——虚拟机不再存在于任何 Hyper-V 主机上。当客户虚拟机恢复时,会创建一个配置相同的新 Hyper-V 虚拟机,很可能在不同的 Hyper-V 主机上。该新 Hyper-V 虚拟机成为恢复的客户虚拟机,Linux 内核从休眠映像恢复所采取的步骤必须在该新虚拟机中起作用。
虽然磁盘及其内容从原始虚拟机中保留下来,但 Hyper-V 提供的磁盘控制器和其他合成设备的 VMBus 实例 GUID 通常会不同。这种差异会导致从休眠中恢复失败,因此需要采取一些措施来解决这个问题
对于仅支持单个实例的 VMBus 合成设备,Hyper-V 总是分配相同的实例 GUID。例如,Hyper-V 鼠标、关机伪设备、时间同步伪设备等,无论是在本地 Hyper-V 安装还是在 Azure 云中,它们始终具有相同的实例 GUID。
VMBus 合成 SCSI 控制器在一个虚拟机中可能存在多个实例,在一般情况下,实例 GUID 会因虚拟机而异。然而,Azure 虚拟机总是精确地拥有两个合成 SCSI 控制器,并且 Azure 代码会覆盖正常的 Hyper-V 行为,因此这些控制器总是被分配相同的两个实例 GUID。因此,当客户虚拟机在新创建的虚拟机上恢复时,实例 GUID 会匹配。但此保证不适用于本地 Hyper-V 安装。
类似地,VMBus 合成网卡在一个虚拟机中可能存在多个实例,并且实例 GUID 会因虚拟机而异。同样,Azure 代码会覆盖正常的 Hyper-V 行为,以确保客户虚拟机中合成网卡的实例 GUID 不会改变,即使客户虚拟机被解除分配或休眠,然后在新创建的虚拟机上重新组成。与 SCSI 控制器一样,此行为不适用于本地 Hyper-V 安装。
在新建虚拟机上从休眠中恢复时,vPCI 设备不具有相同的实例 GUID。因此,Azure 不支持对具有 DDA 设备(如 NVMe 控制器或 GPU)的虚拟机进行休眠。对于 SR-IOV 网卡 VF,Azure 在虚拟机休眠前会从虚拟机中移除 VF,以便休眠映像不包含 VF 设备。当虚拟机恢复时,它会实例化一个新的 VF,而不是尝试匹配休眠映像中存在的 VF。因为 Azure 必须在启动休眠之前移除所有 VF,所以 Azure 虚拟机休眠必须从 Azure 门户或 Azure CLI 外部启动,这反过来又使用关机集成服务来指示 Linux 执行休眠。如果在 Azure 虚拟机内部自行启动休眠,则 VF 会保留在休眠映像中,并且无法正常恢复。