
最近圈子里有一个消息让我挺有感触iWave 在自己基于 NXP i.MX8QM 的模块Module上把 Xen 虚拟化完整演示了起来。这组合放在几年前多半只是实验室里的“概念验证”但现在已经被核心板厂商当成了标准卖点说明在嵌入式设备上谈虚拟化已经从“能不能跑”变成了“怎么落地”。作为一个常年折腾 ARM 平台 hypervisor 的人我第一反应不是去看 iWave 的新闻稿参数而是想搞清楚为什么选 i.MX8QM 做虚拟化演示Xen 在这类模块上到底怎么部署踩过哪些坑的这篇文章把整个思路、启动链、设备树配置和实操过程串一遍适合刚接触嵌入式虚拟化、想在自己的 i.MX8QM 板卡上复现 Xen 的工程师也适合正在评估 Xen 和 KVM 或者 Jailhouse 选型的朋友。1. 先看这次演示的分量iWave的模块组合与Xen选中原因1.1 i.MX8QM 模块到底强在哪i.MX8QM 在 i.MX8 系列里属于“大核”定位它同时包含了两个 Cortex-A72 和四个 Cortex-A53形成典型的 big.LITTLE 大小核架构。A72 跑高负载业务A53 处理轻量任务再加上两个 Cortex-M4F 实时核以及独立的 GPU、ISP、DSP整体算力在当年的车规/工业级芯片里是相当能打的。iWave 把这样一颗 SoC 做到模块上核心板再配上 LPDDR4、eMMC、总之、一个 PMIC 和可选的无线模组底板上只需要接电源和对外接口就能快速整出一块可评估的板子。这块芯片做虚拟化演示有两个天然优势。第一ARMv8-A 的虚拟化扩展在 Cortex-A72/A53 上都具备Xen 可以跑在 EL2 这一层第二i.MX8QM 面向的是汽车域控制器、工业 HMI、医疗影像这类需要“一板多系统”的场景。比如汽车上的信息娱乐域和仪表盘域以前往往是两块主控板现在通过虚拟化可以在同一块 i.MX8QM 上划分出多个独立系统一个跑 Android/HMI一个跑带功能安全认证的 Linux 或 RTOS互不干扰省掉一块板子也省掉大量通信线束。iWave 在这类模块上的做法也比较成熟会把完整启动链配套好SCU 固件、ATF、U-Boot、Linux BSP 都提前适配。所以这次演示 Xen不是硬件工程师临时拿一串命令硬怼更多的像是在给下游客户打个样你看在这个模块上虚拟化已经通了你的项目可以直接基于这套 BSP 做增量开发。1.2 为什么偏偏是 Xen而不是 KVM 或 Jailhouse我在嵌入式虚拟化选型上其实有过很长时间的纠结。KVM 在服务器上很强但到了 ARM 嵌入式场景它本质上要依赖一个完整的 Linux 内核作为宿主然后再启动客户机整体启动链比较长资源消耗也更重。对于需要“上电即跑”、实时性要求高的场景KVM 的调度延迟和复杂度并不划算。Jailhouse 是另一个方向它做的静态分区把核心、内存、设备固定分配给不同 cell性能好、隔离强但灵活性差你不能在运行时像大管家一样动态创建、销毁虚拟机cell 的配置也偏“针脚级”对应用层生态支持几乎为零。Xen 站在两者中间。它是 Type-1 hypervisor直接运行在 EL2不依赖 Linux启动后由 Domain 0简称 dom0负责管理其它 DomainU。Xen 的调度器可以做 CPU 静态分配比如把 A72 集群的核分配给实时域把 A53 集群分配给普通域也可以跑在通用调度模式下动态共享。同时 Xen 又有很多 Linux 生态里的管理工具和驱动xl/xenstored/netback 这些组件能够让客户机方便地使用虚拟块设备、虚拟网络设备这在产品化阶段非常重要客户不想每个域都自己造轮子给你一个虚拟网卡直接通信比共享内存 API 友好太多。另外Xen 的社区对 ARM 的支持越来越完善从 Xilinx ZynqMP 到 i.MX8 系列都有现成的补丁和文档这意味着如果你基于 iWave 模块做产品遇到问题可以从社区拿到支援这对商业项目是很实际的考量。2. 虚拟化移植的核心设计从EL2到设备树的每一处细节2.1 ARM虚拟化扩展和Xen的部署位置很多刚接触 Xen 的人会有一个误解以为 Xen 是跑在 Linux 里的一个软件像 VMware 那样先装系统再装虚拟机。其实 Xen 在所有核还没进入 Linux 之前就已经接管了硬件。在 i.MX8QM 的启动流程里典型顺序是Boot ROM - SCFW系统控制器固件- ATFEL3- U-BootEL2/EL3- XenEL2- dom0 LinuxEL1具体来说i.MX8QM 的 Boot ROM 先把 System Controller UnitSCU的固件加载到 M4 核心负责电源、时钟、管脚和部分外设的低层管理。然后 ATF 在 EL3 启动U-Boot 在 ATF 的引导下运行最后 U-Boot 把 Xen 二进制镜像和设备树加载到内存跳转到 EL2 执行。到这一步Xen 才算真正活起来。Xen 启动后会检查自己是否真的运行在 EL2。如果 U-Boot 或者 ATF 没有正确设置为非安全模式Xen 会直接报错退出。这也是嵌入式平台移植 Xen 时最常见的第一个拦路虎虚拟化扩展或 EL2 入口参数没对上。从架构上看Xen 在 ARM64 上使用了两级地址翻译。客户机domU看到的是“虚拟地址 - IPA中间物理地址”再通过 Stage-2 MMU 翻译到真实物理地址。这种设计最大的价值在于隔离一个 guest 就算内核被攻破它也碰不到其它 guest 的物理内存因为 Stage-2 页表根本不允许它越界。i.MX8QM 的 GICv3 硬件中断控制器加上 Xen 的 vGIC 抽象也能把物理中断安全地路由到对应虚拟机的 vCPU。这里有一个实操要点如果只从官方 BSP 下载 U-Boot 和内核默认配置不一定会编译支持虚拟化扩展。你必须确认 U-Boot 的配置文件里开启了 ARM64 虚拟化模式最好在启动阶段留一个 printk确认当前 Exception Level 是 EL2。否则 Xen 启动时会卡在“unsupported exception level”一类的日志里毫无回旋余地。2.2 设备树怎么改内存、中断、串口Xen 在 ARM 平台不是像 x86 那样靠 ACPI 探测硬件它直接吃设备树。所以在 i.MX8QM 上移植 Xen有一大半工作是在调设备树。这里水非常深我总结下来至少有三个部分必须认真改。第一是添加 hypervisor 自身需要的信息。设备树根节点里要加一个 compatible 为 xen,xen-4.17 之类的节点让 dom0 内核启动时识别到自己是 Xen 的 domain 0。同时还要在 chosen 节点里设置xen,xen-bootargs和xen,dom0-bootargs分别传给 Xen 和 dom0 的 bootargs。第二是为 dom0 精简设备树。dom0 不拥有全部硬件比如某些外设被分配给 domU那么对应节点就要从 dom0 的设备树里删掉或者标记成 status disabled。如果不删dom0 和 domU 就会发生资源争抢轻则驱动冲突重则一个域写重了另一个域的寄存器导致系统崩溃。实际操作时我会先梳理整个设备树里有哪些 memory 节点、中断控制器节点、串口节点、网络节点然后按“Xen 保留一部分dom0 拿一部分domU 拿一部分”的方式重新组织。第三是给每个域准备好各自的设备树源文件。Xen 在 ARM 上支持通过 device_tree 配置给 domU 提供独立设备树。你至少需要为每一个 guest 裁剪出最小可用设备树包含 CPU、内存、GIC、定时器、串口和一个虚拟中断控制器。这块没有现成工具能全自动搞定我一般是复制基础 dts然后用 delete-node / delete-property 一片片删掉不需要的设备节点。一开始会很痛苦但删完以后启动和驱动会清爽很多。2.3 域划分与资源静态分配在 i.MX8QM 上跑 Xen通常不会用像服务器那样的大内存超卖模式而是按“混合关键性系统”的思路做静态分区。比如四个 A53 给一个 Linux 域做业务处理两个 A72 给另一个 RTOS 域做实时控制M4F 核独立跑裸机程序和其它域共享内存但逻辑隔离。Xen 的 xl 配置里可以指定cpus [4, 5]这类格式把 vCPU 绑定到物理 CPU。为了让实时域更稳定我通常还会把调度器改为 Xen 的 null scheduler或者给实时域用静态分配模式。null scheduler 简单粗暴每个物理 CPU 只跑一个 vCPU上下文切换为零适合强实时场景。代价是没有负载均衡你需要在配置阶段想清楚每个域的核资源分布。内存静态分配则体现在memory 1024与maxmem这类参数。Xen 的 dom0 启动参数里可以留一个dom0_mem1G避免 dom0 把内存吃光。而 domU 的内存尽量在启动时直接分配不要让它在小型嵌入式设备上动态抢占内存因为嵌入式内存总量有限动态超卖一旦触发页回收延迟会非常难看。还要注意 i.MX8QM 有一个独立 SCU电源域和时钟域是由 M4 核上的固件管理的Xen 本身并不直接操作所有 PMU 寄存器。因此你在做域划分时需要确保 ATF 和 SCFW 的配置能支持你想启停的 CPU 集群。比如某个 CPU 核已经被 SCFW 关掉那 Xen 即使想把这个核分配给 guest也无法启动对应 vCPU这是一个很多人早期没注意到的坑。3. 在iWave模块上把Xen跑起来完整实操记录3.1 构建环境与镜像准备先说下我用的整体环境一台 Ubuntu 20.04 或 22.04 x86 主机交叉编译工具链是aarch64-linux-gnu系列板子本身用 iWave 的 i.MX8QM 模块 官方载板把串口接到主机。需要准备的东西包括 Xen 源码、Linux 内核源码iWave BSP 内核即可、Buildroot 或 Yocto 生成的 rootfs以及 dtc 设备树编译器。首要的一步是安装依赖sudo apt install -y build-essential flex bison bc libssl-dev \ device-tree-compiler python3 python3-pip uuid-dev \ libyajl-dev libglib2.0-dev libncurses-dev然后是获取 Xen 源码。嵌入式场景我一般不会追最新版而是选一个长期维护版本比如 Xen 4.17 或 4.19。版本选型很重要太老的 Xen 对 GICv3 支持可能不全太新的又要额外适配一些依赖库。git clone https://github.com/xen-project/xen.git -b RELEASE-4.17.0 cd xen编译之前建议先跑一下make menuconfig检查 ARM 特性有没有打开。Xen 的编译命令并不复杂关键是交叉编译变量不能写错make XEN_TARGET_ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -C xen如果编译报缺少 yajl/glib 之类的头文件回去apt install对应开发包就行。最终产物是xen/xen这个裸 ELF 文件后面需要转成 U-Boot 能直接加载的 image 格式或者直接用booti加载。3.2 编译dom0内核和制作rootfsdom0 的内核必须编译进 Xen 相关的驱动。我的做法是先拿 iWave BSP 的完整内核配置做基础然后用make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig打开以下选项CONFIG_XEN_DOM0y CONFIG_XEN_PRIVILEGED_GUESTy CONFIG_XEN_BLKDEV_FRONTENDy CONFIG_XEN_NETDEV_FRONTENDy CONFIG_HVC_DRIVERy CONFIG_HVC_XENy CONFIG_SERIAL_EARLYCONy CONFIG_XEN_BALLOONy如果某些选项在 menuconfig 界面里找不到先确认是否已经开启了CONFIG_XEN。这个总开关不开后面全是白搭。编译命令make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- Image dtbsrootfs 我建议用 Buildroot 直接做比手搓 BusyBox 省事。Buildroot 里把BR2_PACKAGE_XEN打开再把BR2_PACKAGE_XEN_XL、BR2_PACKAGE_XEN_XENSTORE这些用户空间工具选上最后生成 rootfs。注意dom0 里必须要有xl、xl.conf、xenstored和xenconsoled否则启动后只能干瞪眼没法创建 domU。3.3 U-Boot启动参数与首次启动i.MX8QM 的 U-Boot 引导 Xen 时需要把 Xen 二进制、dom0 内核 Image 和 DTB 都放到内存指定地址。因为 Xen 在 ARM64 下本身是一个内核镜像U-Boot 的booti可以直接加载它但设备树要预先设置好 dom0 引导信息。我常用的是这种 U-Boot 脚本思路# 设置加载地址 setenv xen_addr_r 0x84000000 setenv kernel_addr_r 0x8a000000 setenv fdt_addr_r 0x8f000000 setenv ramdisk_addr_r 0x8c000000 # 把镜像从 eMMC 或 tftp 加载到内存 load mmc 1:1 ${xen_addr_r} xen load mmc 1:1 ${kernel_addr_r} Image load mmc 1:1 ${fdt_addr_r} board.dtb load mmc 1:1 ${ramdisk_addr_r} rootfs.cpio.gz # 将 dom0 内核地址写入 DTB 的 module 节点 fdt addr ${fdt_addr_r} fdt set /chosen module0 compatible multiboot,kernel fdt set /chosen module0 reg ${kernel_addr_r} 0x1000000 # 设置 Xen 和 dom0 的启动参数 fdt set /chosen xen,xen-bootargs consoledtuart dtuartserial0 dom0_mem1G fdt set /chosen xen,dom0-bootargs consolehvc0 earlyconxen earlyprintkxen root/dev/ram0 rw # 启动 Xen booti ${xen_addr_r} - ${fdt_addr_r}如果 DTB 里没有module0节点就用fdt mknode /chosen module0先创建。这一步细节多我最初复现时就是因为忘记fdt set reg导致 Xen 找不到 dom0 内核卡在启动阶段。启动后串口应该能看到 Xen 的早期日志然后 dom0 接管控制台。日志里通常会打出Xen 4.17、Xen version以及(XEN) Xen Brought up ...这类关键信息看到这些基本说明 hypervisor 已经成功起来了。3.4 创建并管理DomainU虚拟域dom0 起来后第一件事是确认 Xen 管理工具正常工作xl list如果正常你能看到 dom0 那一行。接下来创建一个最小 domU 配置文件/etc/xen/guest.cfgname guest1 kernel /boot/Image ramdisk /boot/initrd.img machine arm64 memory 512 vcpus 2 cpus [2, 3] device_tree /boot/guest1.dtb console hvc0 extra consolehvc0 root/dev/ram0 rdinit/sbin/init然后启动xl create /etc/xen/guest.cfg使用xl console guest1进入客户机控制台。如果 domU 的设备树没裁好启动会遇到大量缺节点问题这是正常现象。比如没有给 guest 分配 GIC 中断映射它就会报GIC: unknown type或者CPU: failed to boot secondary CPU这类错误。另一种更彻底的嵌入式方案是开启 Xen 的 dom0less。在这种模式下Xen 启动时直接从 DTB 的多个 module 节点加载多个 guest 内核不依赖 dom0 的 xl 管理工具实时创建。这种方式适合产品开机要求快、域配置固定的场景。不过管理动态域能力就弱了你要根据自己的业务决定用哪种。4. 踩坑复盘启动排查与性能调优经验4.1 Xen启动卡住的日志定位说几个我实际遇到过的问题第一个是 Xen 启动后没有任何输出。这种情况十有八九是串口节点还没被 Xen 识别。Xen 在 ARM 上通过设备树里的chosen/stdout-path或者xen,xen-bootargs参数里的dtuart指定早期调试串口。如果直接抄 i.MX8QM 原厂 U-Boot 设备树可能只配置了 Linux 下的serial0但 Xen 要找的是自己解析的 UART 节点。解决办法是给 Xen 传consoledtuart dtuartserial0同时确认serial0在设备树里处于okay状态。如果串口驱动或地址不对最简单的方式是用示波器或逻辑分析仪抓 TX 引脚排除是硬件连接问题还是软件没吐字。第二个问题是 Xen 启动到一半报Could not allocate memory for P2M这类内存相关错误。这通常是因为 dom0 的起始内存地址和 Xen 预留的内存区域重叠。i.MX8QM 的内存布局在不同载板上可能不一致你要根据具体内存 map 修改 Xen 的启动参数比如dom0_mem0x40000000时还要保证让出空间给 Xen 自己使用的dom0_max_vcpus等资源。第三类是 secondary CPU 启动失败卡在(XEN) Failed to boot CPU1。这优先怀疑 PSCI 的问题。i.MX8QM 的 ATF 负责提供 PSCIXen 需要通过 PSCI CPU_ON 把其它核唤醒。如果 ATF 版本和 Xen 不匹配或者 U-Boot 已经提前把某些核心关掉了就会出现这种失败。升级 ATF/U-Boot 到 BSP 厂商最新版本并且不要手动去 SCFW 禁止部分 A 核基本上能解决。4.2 domU创建失败的常见原因domU 创建失败时/var/log/xen/xl.log和xl create -v会给出比较清晰的提示。我遇到的最高频问题是设备树不完整。有些 domU 内核一启动就 panic甚至打印不出任何信息一看日志才知道它根本找不到 timer。这时候我会拿 dom0 的 dts 做基准确认这些节点都在timer节点兼容 arm,armv8-timergic中断控制器节点且能处理 SPI/PPI至少一个uart或虚拟控制台节点memory节点reg 的值要落在 Xen 分配的 IPA 范围内第二个常见问题是xl配置里没有指定device_treeXen fallback 到使用 dom0 的完整设备树给 domU。这种情况下domU 看到的是整个板子的设备树就会尝试初始化 dom0 已经驱动的外设轻则中断冲突重则启动即死。我建议每个 domU 都单独维护一份裁剪后的 dts不要偷懒。第三个典型问题是 rootfs 里的/dev/xen相关设备节点没建立。如果你不是用 devtmpfs而是用了静态设备节点domU 启动后控制台可能显示hvc0不存在。Buildroot 默认 devtmpfs 一般不会碰到但自己拼 rootfs 就会踩到。检查一下/dev/hvc0和xen相关目录是否存在即可。4.3 内存和CPU调优经验虚拟化的性能瓶颈通常不在 CPU 指令执行而在内存访问和中断延迟。i.MX8QM 上的 A72 核心性能足够跑业务里的大多数任务但每次 Stage-2 地址翻译都会增加 TLB miss 的代价。我的建议是尽量少做过度内存超卖每个域的内存大小固定避免频繁的页表回退和气泡回收。对于中断密集场景尽量把物理中断直通给对应域而不是通过 Xen 一层层模拟。比如网卡、GPU 这类 DMA 设备用xl pci-attach或设备树直通方式分配给某域后性能提升非常明显。当然直通带来的另一面是设备不能再被其它域共享这也是架构取舍不是免费午餐。CPU 调优上更实际的操作是给 Xen 指定schednull。这个调度器不会做时间片轮转而是把一个物理 CPU 永久分给一个 vCPU。对嵌入式产品来说如果每个域的核心数能静态规划好null scheduler 带来的低延迟收益远比 credit scheduler 的灵活性重要。不过要注意null scheduler 下如果一个域只分配了一个 vCPU但这个域内又启用了多线程或大量 softirq它依然只有一个核可用配置前先想清楚。5. 最后的一点个人体会回到这一次 iWave 的演示我认为真正值得借鉴的不是某个具体工具链而是他们把虚拟化从“可以启动”做到了“可以被客户使用”。在 i.MX8QM 这种模块上跑 Xen最耗时间的永远是设备树裁剪、U-Boot 引导参数、域资源分配这些脏活。如果你正在评估虚拟化方案建议先别急着看 benchmark先拿一块 iWave 模块把xl list跑通再尝试把简单的 Linux guest 启动起来。只要链路过一遍后面再加 GPU 直通、实时 CPU 绑定、共享内存这些高级特性都会顺畅很多。我自己在复现这类工程时最大的习惯是每一步都保留日志每次改启动参数只改动一个变量。因为虚拟化栈的问题往往分层产生显示在 hypervisor、根因在设备树、表象在客户机驱动。能定位到是哪一层出了问题比多写一百行配置都管用。希望这篇文章能帮你少走一些弯路。