aarch64 KVM 来宾在早期 linux 启动时挂起

aarch64 KVM guest hangs on early linux boot

我正在尝试设置一个非常小的支持 aarch64 KVM 的系统。

我的设计需要一个极简内核,只有很少的驱动程序链接到内核映像。 我的 objective 是尽快启动虚拟机 运行 裸机应用程序。 稍后需要相同的管理程序才能 运行 成熟的 Linux 分发。

我碰巧当这个 aarch64 hypervisor 启动一个 Linux VM with qemu -M virt,accel=kvm VM 执行引导加载程序,内核的 efi 存根,但挂在内核的 arch-specific 初始化中。 更准确的说,运行打开qemu窥视挂起的系统,我发现PC经常在这个位置附近:

U-Boot 2021.01 (Aug 20 2021 - 10:50:56 +0200)

DRAM:  128 MiB
Flash: 128 MiB
In:    pl011@9000000
Out:   pl011@9000000
Err:   pl011@9000000
Net:   No ethernet found.
Hit any key to stop autoboot:  0 
Timer summary in microseconds (7 records):
       Mark    Elapsed  Stage
          0          0  reset
815,990,026815,990,026  board_init_f
816,418,047    428,021  board_init_r
818,760,551  2,342,504  id=64
818,763,069      2,518  main_loop

Accumulated time:
                10,017  dm_r
                53,553  dm_f
10189312 bytes read in 111 ms (87.5 MiB/s)
Scanning disk virtio-blk#31...
Found 1 disks
Missing RNG device for EFI_RNG_PROTOCOL
No EFI system partition
Booting /\boot\Image
EFI stub: Booting Linux Kernel...
EFI stub: Using DTB from configuration table
EFI stub: Exiting boot services and installing virtual address map...
QEMU 5.2.0 monitor - type 'help' for more information
(qemu) info registers 
 PC=ffffffc010010a34 X00=1de7ec7edbadc0de X01=ffffffc01084ca38
X02=ffffffc01084ca38 X03=1de7ec7edbadc0de X04=ffffffc0108f0120
X05=0000000000000348 X06=ffffffc010679000 X07=0000000000000000
X08=ffffffc010a079b8 X09=ffffffc01008ef80 X10=ffffffc0108ee398
X11=ffffffc010000000 X12=ffffffc01099a3c8 X13=0000000300000101
X14=0000000000000000 X15=0000000046df02b8 X16=0000000047f6d968
X17=0000000000000000 X18=0000000000000000 X19=ffffffc0109006c0
X20=1de7ec3eec328b16 X21=ffffffc0108f02b0 X22=ffffffc01073fd40
X23=00000000200001c5 X24=0000000046df0368 X25=0000000000000001
X26=0000000000000000 X27=0000000000000000 X28=ffffffc0109006c0
X29=ffffffc0108f0090 X30=ffffffc01065abf4  SP=ffffffc01084c2b0
PSTATE=400003c5 -Z-- EL1h     FPCR=00000000 FPSR=00000000
Q00=0000000000000000:0000000000000000 Q01=0000000000000000:0000000000000000
Q02=0000000000000000:0000000000000000 Q03=0000000000000000:0000000000000000
Q04=0000000000000000:0000000000000000 Q05=0000000000000000:0000000000000000
Q06=0000000000000000:0000000000000000 Q07=0000000000000000:0000000000000000
Q08=0000000000000000:0000000000000000 Q09=0000000000000000:0000000000000000
Q10=0000000000000000:0000000000000000 Q11=0000000000000000:0000000000000000
Q12=0000000000000000:0000000000000000 Q13=0000000000000000:0000000000000000
Q14=0000000000000000:0000000000000000 Q15=0000000000000000:0000000000000000
Q16=0000000000000000:0000000000000000 Q17=0000000000000000:0000000000000000
Q18=0000000000000000:0000000000000000 Q19=0000000000000000:0000000000000000
Q20=0000000000000000:0000000000000000 Q21=0000000000000000:0000000000000000
Q22=0000000000000000:0000000000000000 Q23=0000000000000000:0000000000000000
Q24=0000000000000000:0000000000000000 Q25=0000000000000000:0000000000000000
Q26=0000000000000000:0000000000000000 Q27=0000000000000000:0000000000000000
Q28=0000000000000000:0000000000000000 Q29=0000000000000000:0000000000000000
Q30=0000000000000000:0000000000000000 Q31=0000000000000000:0000000000000000
(qemu) x/10i 0xffffffc010010a34
0xffffffc010010a34:  8b2063ff      add sp, sp, x0
0xffffffc010010a38:  d53bd040      mrs x0, (unknown)
0xffffffc010010a3c:  cb2063e0      sub x0, sp, x0
0xffffffc010010a40:  f274cc1f      tst x0, #0xfffffffffffff000
0xffffffc010010a44:  54002ca1      b.ne #+0x594 (addr -0x3feffef028)
0xffffffc010010a48:  cb2063ff      sub sp, sp, x0
0xffffffc010010a4c:  d53bd060      mrs x0, (unknown)
0xffffffc010010a50:  140003fc      b #+0xff0 (addr -0x3feffee5c0)
0xffffffc010010a54:  d503201f      nop
0xffffffc010010a58:  d503201f      nop

https://elixir.bootlin.com/linux/v5.12.10/source/arch/arm64/kernel/entry.S#L113

来宾内核是上游5.12,arch 初始化区域没有任何修改。 start_kernel() 中只有一些更改,其中添加了一些 printk() 以提供时间戳,但 VM 永远不会到达它们。 此外,同一个虚拟机管理程序可以 运行 来宾具有相同的 qemu,只需删除 KVM 加速。

我对 KVM 有一点经验,对 aarch64 上的 KVM 了解较少。 我觉得我在管理程序内核中遗漏了一些东西。 我从内核中删除了许多驱动程序以获得最快的启动时间,因此没有 USB 或网络驱动程序,但我确保内核启用了完整的虚拟化套件配置。

当系统启动时,/dev/kvm 就在那里,而且 qemu 似乎浅化了我的命令,包括 KVM 功能而没有抱怨。

但是后来主机挂了。

我在两个不同的平台上得到了这些结果,只是为了确保它不依赖于硬件:

目前,我不知道下一步要调查哪里。任何建议将不胜感激。

最后,我在上面描述的环境中成功地制作了 KVM 主机 运行。

这里恢复问题:

  • 最小 Linux 支持 KVM
  • 最小 Linux 系统及其引导加载程序和内核映像 运行 在虚拟化环境中
  • Linux 内核在 start_kernel() 中稍作修改,添加了一些 printk() 以发出时间戳
  • 图像被证明在 qemu 上工作,没有 KVM 扩展名

使用 qemuKVM 扩展进行测试得到以下结果:

作为 qemu 开始

qemu-system-aarch64 -machine virt -cpu host -enable-kvm -smp 1 -nographic -bios ./u-boot.bin -drive file=./rootfs.ext2,if=none,format=raw,id=hd0 -device v
irtio-blk-device,drive=hd0

引导加载程序被执行,流程被交给 Linux 内核,Linux 内核 EFI 存根发出它的消息,消息流停止,一切似乎都挂起。

随机窥视执行,我验证了代码是 始终在 /arch/arm64/kernel/entry.S.

对这段代码的快速检查让我得出错误的结论,这段代码是一个循环的一部分,由一些奇特的(对我而言)aarch64 特定控制寄存器控制。

我在 Whosebug 上提出了我的问题

再看一遍,发现没有循环,是异常链造成的。

显然,我对内核的修改添加了 printk() 尽早在内核正确设置其堆栈之前添加了这段代码。

此修改是问题的根源

我从这个问题中学到的一些东西:

  • 随机查看代码,在同一指令周围找到它并不意味着代码在循环中
  • 让代码明显在环境中工作并不意味着代码是正确的
  • 仿真和虚拟化旨在提供相同的结果,但它们不太可能达到 100% 的情况。如果您不想坚持错误的假设,请考虑它们的不同之处。

一半以上的测试,我用的都是Pine64的板子。我拥有的 Pine64 基于 Allwinner A64 SoC。该板及其主要 SoC 上没有很多文档。此外,可用的文档很旧。我担心它的启动顺序,因为网上有一个 document 说因为启动链的一个专有组件在 EL1 释放处理器,所以 KVM 无法正常工作。这个事实促使我深入研究 Pine64 启动过程。

我在Pine64板和KVM上学到的东西:

  • 该文档是指Allwinner提供的早期BSP。该组件不是引导 ROM。基于 ATF 的引导链的较新组件不会遇到此问题。详情见下方代码框
  • Allwinner A64 SoC 是 Cortex-A53,与任何其他 ARMv8 一样,在 运行ning KVM 或任何其他虚拟化工具中没有设计问题。
  • 在 aarch64 上,内核可以从 EL2 或 EL1 开始。但是,如果您需要使用 KVM 进行虚拟化,则需要从 EL2 开始,因为这是管理程序 (KVM) 可以执行其工作的异常级别。
  • U-boot 在 EL2 启动内核;有一个特定的配置可以让它在 EL1 启动内核,这是 不是默认的
  • 启动内核安装KVM hypervisor代码,在start_kernel()函数前将CPU改为EL1
U-Boot SPL 2021.01 (Aug 31 2021 - 10:14:46 +0200)
DRAM: 512 MiB
Trying to boot from MMC1


U-Boot 2021.01 (Aug 31 2021 - 10:13:59 +0200) Allwinner Technology

EL = 2
CPU:   Allwinner A64 (SUN50I)
Model: Pine64
DRAM:  512 MiB
MMC:   mmc@1c0f000: 0
Loading Environment from FAT... *** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   phy interface6
eth0: ethernet@1c30000
starting USB...
Bus usb@1c1a000: USB EHCI 1.00
Bus usb@1c1a400: USB OHCI 1.0
Bus usb@1c1b000: USB EHCI 1.00
Bus usb@1c1b400: USB OHCI 1.0
scanning bus usb@1c1a000 for devices... 1 USB Device(s) found
scanning bus usb@1c1a400 for devices... 1 USB Device(s) found
scanning bus usb@1c1b000 for devices... 1 USB Device(s) found
scanning bus usb@1c1b400 for devices... 1 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  0
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
270 bytes read in 2 ms (131.8 KiB/s)
## Executing script at 4fc00000
32086528 bytes read in 1541 ms (19.9 MiB/s)
27817 bytes read in 4 ms (6.6 MiB/s)
Moving Image from 0x40080000 to 0x40200000, end=42120000
## Flattened Device Tree blob at 4fa00000
   Booting using the fdt blob at 0x4fa00000
EHCI failed to shut down host controller.
   Loading Device Tree to 0000000049ff6000, end 0000000049fffca8 ... OK
EL = 2

Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
[    0.000000] Linux version 5.12.19 (alessandro@x1) (aarch64-buildroot-linux-gnu-gcc.br_real (Buildroot 2021.05.1) 9.4.0, GNU ld (GNU Binutils) 2.35.2) #2 SMP PREEMPT Mon Aug 30 20:06:45 CEST 2021
[    0.000000] EL = 1
[    0.000000] Machine model: Pine64
[    0.000000] efi: UEFI not found.