热迁移后虚拟机卡死、CPU 100%,往往是时间处理出了问题。用 crash 看 dump 能确认死循环卡在内核路径,再用 perf 在虚拟机内和宿主机上对比,就能定位到时间中断处理异常。

现象

把一台虚拟机从 Ubuntu 20 宿主机热迁移到 Ubuntu 22 宿主机后,虚拟机卡死了。

从宿主机看,virsh migrate 是成功的,虚拟机处于 running 状态,qemu 进程也正常。但被迁移的虚拟机 CPU 占用 100%,完全无响应。

排查

用 crash 分析 virsh dump 出来的内存转储,发现内核一直在刷下面的报错,看起来是死循环卡在了调度进程 swapper 上:

[7686175795.936645] Call Trace:
[7686175795.936647]  <IRQ>
[7686175795.936651]  run_timer_softirq+0x2a/0x50
[7686175795.936655]  __do_softirq+0xd1/0x2c1
[7686175795.936658]  irq_exit+0xae/0xb0
[7686175795.936660]  smp_apic_timer_interrupt+0x7b/0x140
[7686175795.936662]  apic_timer_interrupt+0xf/0x20
[7686175795.936662]  </IRQ>
[7686175795.936664] RIP: 0010:native_safe_halt+0xe/0x10

[7686175823.935201] watchdog: BUG: soft lockup - CPU#0 stuck for 23s! [swapper/0:0]

0 号进程就是 swapper 进程,也就是调度进程。

这种 CPU 100% 表现出来的卡死,必然是因为某种原因让 CPU 进入了死循环——CPU 被占住放不出来,无法执行其他进程,所以表现出来就是"卡死"。

这种问题必须用 perf 来分析,分别在虚拟机内和宿主机上进行分析对比:

# 虚拟机内
perf record -e msr:write_msr -g -a
perf script

根因

大概原因是:

  1. 虚拟机没配置 CPU 的情况下,是从一个精度很低的时间设备上维持高精度计时器;
  2. 如果虚拟机配置了 CPU,则是通过特定的指令集 rdtsc 指令从物理 CPU 读取计数器。

在热迁移过程中,时间处理这方面存在问题,导致迁移完成后,虚拟机卡死在时间中断处理流程上。

小结

迁移和扩容,时间处理都是常出问题的地方。

这类问题排查的关键在于:

  • 用 crash 分析 dump,确认是不是死循环、卡在哪个内核路径上;
  • 用 perf 在虚拟机内和宿主机上对比分析,定位时间处理相关的异常;
  • 热迁移涉及 CPU 状态、时间源、中断等底层细节,任何一个环节不一致都可能引发迁移后的"假死"。