
1. 项目概述与核心价值如果你正在从事嵌入式系统开发尤其是涉及汽车电子、工业控制或5G通信设备这类对实时性和可靠性要求极高的领域那么你肯定对“既要、又要、还要”的困境深有体会。我们常常需要在同一块硬件上既要运行一个功能丰富、生态完善的通用操作系统比如Linux来处理复杂的应用逻辑和网络通信又要确保一个对时间极度敏感的关键任务比如电机控制、安全制动能够在一个确定性的实时操作系统RTOS上毫秒不差地执行。传统的做法要么是使用两颗独立的芯片成本高昂要么是在一个OS上打补丁实时性难以保障系统复杂度剧增。最近openEuler社区开源了一个名为ZVMZephyr-based Virtual Machine的新项目正好瞄准了这个痛点。简单来说ZVM是一个基于实时操作系统Zephyr开发的嵌入式实时虚拟机。它的核心能力是让你可以在一个ARMv8-A架构的处理器上同时、隔离地运行两个客户操作系统Guest OS一个是功能完备的Linux另一个是极致实时的Zephyr RTOS。这相当于给你的硬件平台赋予了“人格分裂”的能力让Linux和Zephyr这两个性格迥异的“租客”在同一套“房子”硬件里和谐共处互不干扰且各自发挥所长。我第一次接触到这个项目时最让我兴奋的不是“虚拟化”这个概念本身——这在服务器领域早已成熟——而是它将虚拟化技术真正下沉到了资源受限、对功耗和确定性要求严苛的嵌入式场景。由湖南大学嵌入式与网络计算湖南省重点实验室主导开发的ZVM其技术路线选择非常务实它没有选择从零造轮子而是以经过工业验证的Zephyr RTOS为底座的“主机”Host在其上构建虚拟化层。这样做的好处是底座的实时性和可靠性有了坚实基础而上层的虚拟化功能可以专注解决嵌入式场景的特有问题比如极低的中断延迟、确定性的任务调度以及对非标准外设的灵活管理。对于开发者而言ZVM带来的价值是实实在在的。首先它实现了硬件整合可以将原本需要多颗芯片的系统集成到单颗性能更强的SoC上直接降低了BOM成本和物理空间。其次安全隔离是关键Linux域中复杂的网络协议栈或图形应用万一崩溃不会影响到Zephyr域中生死攸关的实时控制任务这极大地提升了系统整体的可靠性和安全性。最后它提供了极大的设计灵活性你可以根据产品生命周期的不同阶段动态调整分配给两个域的计算和内存资源甚至在未来可以想象在Zephyr域中运行不同安全等级的任务实现更细粒度的隔离。2. 嵌入式实时虚拟化的挑战与ZVM的设计哲学在深入ZVM的技术细节之前我们有必要先厘清为什么在嵌入式领域做虚拟化尤其是实时虚拟化会如此困难。这和我们熟悉的云服务器虚拟化如KVM、Xen面临的挑战有本质不同。服务器虚拟化的核心目标是资源整合与利用率最大化其性能开销通常为5%-15%在充沛的CPU和内存资源面前是可以接受的。但嵌入式虚拟化特别是实时虚拟化其首要目标是保证确定性Determinism和低延迟Low Latency其次才是资源共享。这里的挑战是根本性的2.1 挑战一如何在不破坏实时性的前提下实现强隔离实时系统的生命线是中断响应时间和任务调度延迟的可预测性。传统的Type-1型虚拟机监控器Hypervisor本身作为一个软件层其调度决策、中断处理和内存管理都会引入不可预测的延迟。如果Hypervisor的代码路径过长或者其调度器不是确定性的那么运行在其上的RTOS的“实时性”将无从谈起。ZVM的选择是“轻量化”和“硬件辅助”。它基于Zephyr这个本身就以小巧、实时著称的RTOS进行改造将虚拟化层深度融入内核而非作为一个独立的、庞大的软件层叠加其上。同时它重度依赖ARMv8-A的硬件虚拟化扩展如VHE ARM虚拟化主机扩展将大量的隔离和切换工作交给硬件完成从而将软件开销降到最低。2.2 挑战二如何高效、灵活地管理I/O设备嵌入式设备外设繁多UART、I2C、SPI、CAN、Ethernet等且对它们的访问延迟非常敏感。虚拟化I/O有两种经典模式全虚拟化模拟和直通Passthrough。全虚拟化由Hypervisor模拟一个虚拟设备兼容性好但性能差、延迟高直通将物理设备直接分配给某个Guest OS性能无损但设备无法共享。ZVM采用了混合策略这是非常务实的工程设计。对于中断控制器GIC、定时器这类必须由Hypervisor集中管理的独占性资源它采用全虚拟化。而对于像UART这样的简单字符设备或者某些对实时性要求极高的专用外设如PWM则可以采用直通模式直接划给Zephyr Guest确保其访问的零开销和确定性。这种按需分配的策略在保证系统功能完整性的同时最大程度地保障了关键任务的性能。2.3 挑战三如何最小化虚拟化带来的性能开销虚拟化开销主要来自三个方面CPU上下文切换、内存地址转换和中断处理。每一次在Guest OS和Host之间的切换VM Exit/Entry都需要保存和恢复大量的CPU状态这在频繁交互的场景下是灾难性的。ZVM利用ARM的VHE特性巧妙地将Zephyr Host本身运行在更高的特权级EL2而Guest OS运行在EL1/0。这样在处理许多敏感指令和中断时可以避免昂贵的世界切换World Switch部分操作在EL2内即可完成大幅减少了上下文保存/恢复的开销。在内存方面它利用ARM的两阶段页表转换Stage-2 Translation由内存管理单元MMU硬件直接完成Guest物理地址到Host物理地址的转换几乎不增加软件负担。ZVM的设计哲学可以概括为以实时性为基石以硬件辅助为杠杆以混合管理为手段。它不追求大而全的虚拟化功能而是紧紧围绕嵌入式实时系统的核心诉求——确定性、低延迟、安全隔离——来构建其技术栈。理解了这一点我们再看它的系统架构就会觉得每一步都有的放矢。3. ZVM系统架构深度解析ZVM的架构可以看作是对Zephyr RTOS的一次“虚拟化增强”手术。它的目标不是创建一个全新的Hypervisor而是让Zephyr自身具备托管其他操作系统的能力。下图勾勒了其核心架构思想注此处为文字描述替代原图--------------------------------------------------------------- | Guest OS (Linux) | | (非实时丰富生态) | --------------------------------------------------------------- | Guest OS (Zephyr) | | (实时关键任务) | -------------------------------------------------------------- | | ZVM 虚拟化层 | | CPU虚拟化 (vCPU) | 内存虚拟化 (Stage-2) | | 中断虚拟化 (vGIC) | 定时器虚拟化 (vTimer) | | 设备虚拟化 (MMIO) | I/O 管理 (模拟/直通) | -------------------------------------------------------------- | Zephyr RTOS (Host) | | (作为VMM提供基础调度、驱动和实时性保障) | --------------------------------------------------------------- | ARMv8-A 硬件平台 | | (CPU with VHE, GIC, Stage-2 MMU, Generic Timer) | ---------------------------------------------------------------接下来我们逐一拆解图中的关键虚拟化模块看看ZVM是如何具体实现“让两个OS和谐共处”的。3.1 CPU虚拟化vCPU与调度在ZVM中每个Guest OS的每个CPU核心都被抽象为一个虚拟CPUvCPU。对于Linux Guest如果你给其分配了2个vCPU那么在Linux看来它就在一个双核处理器上运行。这些vCPU在ZVM中的实体实际上是一个个Zephyr线程。这里有一个关键设计点为什么不把vCPU设计成独立的调度实体而是复用Zephyr的线程答案是为了极致轻量和继承实时性。Zephyr内核本身就是一个优秀的实时调度器支持优先级抢占、时间片轮转等。将vCPU映射为线程意味着ZVM可以直接利用Zephyr成熟、确定的调度策略来调度各个Guest OS。例如你可以将运行关键实时任务的Zephyr Guest的vCPU线程优先级设为最高确保它总能抢占Linux Guest的vCPU线程从而在CPU时间分配上保障实时性。ARM VHEVirtualization Host Extensions的运用是另一大亮点。通常Hypervisor运行在EL2Guest OS运行在EL1。没有VHE时Host OS如果它本身是个通用OS的代码需要大量修改才能运行在EL2。VHE允许一个“主机操作系统”以最小的修改运行在EL2。ZVM正是利用这一点将作为Host的Zephyr RTOS运行在EL2。这样做的好处是当需要处理一个原本需要陷入到EL2的异常时例如Guest OS访问了某个受保护的寄存器由于Zephyr已经在EL2它可以直接处理而无需进行昂贵的异常级别切换极大地降低了陷入-模拟Trap-and-Emulate的开销。3.2 内存虚拟化两阶段地址转换与隔离内存隔离是系统安全的基石。ZVM利用ARM架构的第二阶段地址转换Stage-2 Translation来实现Guest OS之间的内存隔离。这个过程分为两步第一阶段Stage-1由Guest OS内核控制。它将Guest OS内的应用程序虚拟地址VA转换为Guest OS认为的物理地址GPA。这一步和没有虚拟化时完全一样。第二阶段Stage-2由ZVM控制。它将Guest OS的物理地址GPA转换到真实的机器物理地址HPA。ZVM为每个Guest OS维护一套独立的Stage-2页表。当Guest OS访问内存时MMU硬件会自动、并行地查询这两套页表完成VA-GPA-HPA的转换。如果Guest OS试图访问一段未被ZVM分配给它的物理内存Stage-2页表会给出一个错误触发一个异常被ZVM捕获从而阻止非法访问。这种硬件辅助的隔离机制效率远高于纯软件的内存保护方案。3.3 中断虚拟化虚拟中断控制器vGIC与注入中断是实时系统的命脉。在虚拟化环境中所有物理中断首先由HostZVM接收。ZVM需要决定将这个中断转发给哪个Guest OS甚至模拟出虚拟中断。ZVM基于ARM的通用中断控制器GIC实现了虚拟GICvGIC。物理GIC负责分发硬件中断而vGIC则负责管理“虚拟中断”的生命周期。例如Zephyr Guest中运行的定时器任务到期了这个“事件”需要以中断的形式通知Zephyr Guest的vCPU。这个过程就是中断注入。ZVM主要通过两种机制实现中断注入List Register (LR)这是GICv2架构中的一种硬件机制。ZVM可以将一个虚拟中断的信息如ID、优先级、目标vCPU写入物理CPU的LR寄存器。当该vCPU被调度运行时硬件会自动“看到”这个 pending 的虚拟中断仿佛它是从硬件直接产生的一样延迟极低。Virtual CPU Interface在GICv3中功能更加强大为每个vCPU提供了完整的虚拟接口寄存器组。通过vGICZVM能够精细地管理中断路由、优先级和状态确保实时Guest的中断能够得到最优先、最快速的响应。3.4 定时器虚拟化保持时间的真实性定时器是RTOS心跳的来源。虚拟化环境下Guest OS的定时器可能会因为自己被调度出去即它的vCPU线程未运行而停滞这会导致其时间概念失真。ZVM的定时器虚拟化模块需要解决这个问题。ARM架构提供了物理的通用定时器Generic Timer。ZVM为每个vCPU虚拟出一套定时器寄存器如CNTV_CTL_EL0,CNTV_CVAL_EL0。当Zephyr Guest设置一个1ms的定时器时ZVM会将其转换为一个基于物理定时器的Host侧事件。更重要的是在vCPU线程被调度出去descheduled时ZVM会保存虚拟定时器的当前值当vCPU再次被调度进来时ZVM会补偿这段时间并计算是否需要立即触发一个虚拟定时器中断。这样尽管Guest OS的执行被物理时间打断了但从它自身的视角看它的定时器始终在准确、连续地走动。3.5 设备虚拟化MMIO陷阱与混合模型设备访问主要通过内存映射I/OMMIO进行。ZVM会为每个Guest OS精心构造一段“虚拟设备”内存区域。当Guest OS尝试访问这段区域时比如执行一条STR指令向某个“设备寄存器”地址写数据CPU会触发一个Stage-2转换错误异常因为该GPA在Stage-2页表中被标记为“无效”或“需要陷入”。ZVM捕获到这个异常解析访问的地址和操作然后模拟Emulate这个设备访问行为。例如如果Guest OS写的是虚拟UART的数据寄存器ZVM可能会将这个字符数据复制到Host Zephyr管理的真实UART发送缓冲区中。这就是全虚拟化模拟设备的工作方式。对于直通设备ZVM的Stage-2页表会将该设备所在的物理内存区域直接、一对一地映射给某个Guest OS。Guest OS的驱动将直接与硬件对话性能无损。ZVM只需要在初始配置时做好映射并在调度时确保设备访问的原子性例如在切换出拥有直通设备的Guest时需确保其DMA操作已完成。4. 实战在QEMU上体验ZVM理论说得再多不如亲手跑一遍。目前ZVM项目仍在快速发展中但我们已经可以在模拟器上搭建环境体验其核心功能。以下步骤基于openEuler社区提供的文档和代码库我将其整理并补充了实操中可能遇到的细节。4.1 环境准备与源码获取首先你需要一个x86_64的Linux开发机Ubuntu 22.04或openEuler 22.03 LTS均可。我们使用QEMU来模拟一个ARMv8-A的硬件环境。# 1. 安装基础依赖 sudo apt-get update sudo apt-get install -y git wget make gcc g python3-pip \ ninja-build pkg-config libglib2.0-dev libpixman-1-dev \ flex bison libssl-dev # 2. 获取ZVM及相关子项目源码 # ZVM主仓库 git clone https://gitee.com/openeuler/zvm.git cd zvm # 初始化并同步子模块非常重要Zephyr和Linux内核作为子模块引入 git submodule update --init --recursive注意由于网络问题同步west管理的Zephyr子模块可能会较慢或失败。可以尝试配置git代理或者耐心重试。这是搭建Zephyr相关项目常见的“第一道坎”。4.2 构建Host (Zephyr) 与 Guest OSZVM的构建系统基于Zephyr的west工具进行扩展。它需要分别构建作为Host的Zephyr固件和作为Guest的Linux内核镜像。# 进入ZVM目录 cd zvm # 3. 安装Python依赖及west工具 pip3 install --user -r zephyr/scripts/requirements.txt export PATH$HOME/.local/bin:$PATH # 4. 导出Zephyr开发环境变量 source zephyr/zephyr-env.sh # 5. 配置并构建ZVM (Host侧) # 我们以模拟ARM Cortex-A53的QEMU目标为例 west build -b qemu_cortex_a53 -d build_zvm ./zvm # 构建完成后Host镜像位于 build_zvm/zephyr/zephyr.elf # 6. 构建Linux Guest内核 # 进入Linux子模块目录使用ZVM提供的配置 cd guests/linux mkdir build cd build # 使用aarch64交叉编译工具链需要提前安装如gcc-aarch64-linux-gnu make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- defconfig make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc) # 构建完成后内核镜像为 arch/arm64/boot/Image实操心得构建Linux内核时务必确认你的交叉编译工具链前缀CROSS_COMPILE是正确的。在Ubuntu上通常是aarch64-linux-gnu-对应的包名为gcc-aarch64-linux-gnu。如果遇到找不到命令的错误请先安装对应的工具链。4.3 制作根文件系统与启动脚本Linux Guest需要一个根文件系统rootfs才能启动。我们可以使用BusyBox制作一个极简的initramfs。# 7. 回到ZVM根目录使用项目提供的脚本制作rootfs cd ../.. # 回到zvm根目录 ./scripts/mkrootfs.sh # 该脚本会下载BusyBox源码编译并生成一个cpio格式的initramfs镜像rootfs.cpio接下来我们需要编写一个QEMU启动脚本将HostZVM、Linux内核和rootfs组合起来。# 8. 创建启动脚本 run_zvm.sh cat run_zvm.sh EOF #!/bin/bash QEMU_SYSTEM_AARCH64qemu-system-aarch64 # 指定模拟的机器类型为 virtCPU为 cortex-a53 MACHINE-machine virt,highmemoff -cpu cortex-a53 -smp 2 -m 2G # 启用KVM加速如果宿主机支持且是x86架构这里不能用对于ARM模拟我们使用TCG # 如果是物理ARM开发板调试可以移除-nographic连接串口 GRAPHICS-nographic # 加载ZVM Host固件 FIRMWARE-kernel ./build_zvm/zephyr/zephyr.elf # 设备树QEMU的virt机器会自动生成ZVM内部会动态修改它以供Guest使用 # 我们不需要额外指定dtb。 # 启动命令通过ZVM的内置命令加载Linux Guest # 注意这里是一种简化示意。实际ZVM可能通过修改设备树节点或命令行参数传递Guest信息。 # 请以项目最新文档和代码为准。 APPEND-- -kernel ./guests/linux/build/arch/arm64/boot/Image -initrd ./rootfs.cpio ${QEMU_SYSTEM_AARCH64} ${MACHINE} ${GRAPHICS} ${FIRMWARE} ${APPEND} EOF chmod x run_zvm.sh重要提示上述启动脚本是概念性的。ZVM实际引导Guest OS的机制可能更加复杂例如通过Flattened Device Tree (FDT)向Host传递Guest的配置信息或者Host在启动后通过其控制台动态加载Guest。请务必查阅项目README.md或docs/目录下的最新文档获取准确的启动命令。项目的快速入门指南可能会提供一个一步到位的west build -t run命令。4.4 运行与观察如果一切顺利执行启动脚本./run_zvm.shQEMU窗口会首先启动Zephyr (Host)你会在串口输出中看到Zephyr的启动日志和ZVM的初始化信息。随后ZVM会开始加载Linux Guest。如果成功你将看到Linux内核的启动信息最后进入BusyBox的shell。此时你就在一个由Zephyr RTOS托管的虚拟化环境中运行起了一个完整的Linux系统。你可以尝试在Linux shell中执行一些基本命令ls,ps同时思考在“后台”的Zephyr Host仍在确定性地运行着它的实时任务。5. 开发、调试与性能调优要点将ZVM应用到实际项目仅仅能跑起来是远远不够的。下面分享一些在开发和调试过程中积累的经验和需要关注的要点。5.1 为你的硬件移植ZVMZVM目前主要支持QEMUvirt平台和少数参考开发板。要移植到自己的ARMv8-A硬件上你需要创建Board目录在zvm/boards/arm/下复制一份最接近你硬板的配置如qemu_cortex_a53。修改设备树DTS这是最关键的一步。你需要正确描述你的硬件内存布局、中断控制器GIC、定时器、UART等核心外设。ZVM的HostZephyr和Guest Linux都依赖于此。配置内存区域在DTS和链接脚本中明确划分出Host Zephyr使用的内存、Linux Guest使用的内存、以及共享内存区域如果需-要。确保Stage-2页表配置与此一致。驱动适配确保Host Zephyr的串口、定时器等基础驱动在你的板子上工作正常。Guest Linux的驱动通常通过设备树传递或使用直通模式。避坑指南移植初期最容易出现的问题是内存映射错误导致Guest OS一启动就访问非法内存而崩溃。务必使用QEMU的-d mmu参数来输出详细的MMU访问日志结合ZVM的调试输出精确定位Stage-2转换失败的地址。5.2 调试技巧双系统下的问题定位调试混合系统比调试单一系统复杂得多。你需要清晰地知道问题发生在哪个“世界”。Host (Zephyr) 调试串口日志确保Host的调试串口通常是UART0输出足够详细的日志。在prj.conf中启用CONFIG_LOGy并提高日志级别CONFIG_LOG_DEFAULT_LEVEL4。GDB调试通过QEMU的-s -S参数启动然后使用aarch64-zephyr-elf-gdb连接进行单步调试。这对于分析ZVM初始化流程、异常处理函数非常有效。Guest (Linux) 调试早期控制台确保Linux内核的早期控制台earlycon指向正确的虚拟UART地址。这个地址需要在传递给Linux的设备树中与ZVM模拟的虚拟UART设备匹配。KGDB在Linux内核中启用KGDB并通过虚拟串口进行远程调试。你需要确保ZVM的虚拟UART驱动支持KGDB所需的轮询polling模式。交互与状态检查ZVM应该会提供一个管理控制台可能是通过某个特定的UART。通过它你可以动态查看各个vCPU的状态、内存映射情况甚至动态启停Guest OS。熟悉这些管理命令是运维的关键。5.3 性能分析与实时性保障评估ZVM方案是否满足你的实时需求必须进行量化测试。中断延迟测试测试方法在Zephyr Guest中创建一个高优先级任务该任务在一个GPIO引脚上产生一个外部中断在QEMU中可以用虚拟设备模拟。在Host侧或另一个核心上用另一个GPIO检测该中断信号。测量从物理中断发生到Zephyr Guest中断服务程序ISR开始执行的时间差。工具需要高精度示波器或逻辑分析仪。在模拟环境中可以使用QEMU的-icount和-trace功能进行近似分析但物理测试更可靠。优化方向如果延迟过大检查是否因Linux Guest的vCPU正在运行而导致Zephyr Guest的vCPU被抢占。可以尝试将Zephyr Guest的vCPU线程优先级设为最高并调整Zephyr调度器的时间片。上下文切换开销测试测试方法在Host Zephyr中创建两个不同优先级的任务分别绑定到两个物理CPU核心。测量高优先级任务就绪到它开始执行的时间即抢占延迟。然后在两个Guest OS的vCPU线程上重复此测试对比开销。解读vCPU线程之间的切换开销应略高于普通Zephyr任务切换因为涉及更多CPU状态如系统寄存器的保存/恢复。ARM的VHE特性旨在减少这部分开销。内存访问性能使用类似lmbench的工具在Linux Guest内运行测量内存带宽和延迟。与原生Linux运行结果对比评估Stage-2页表转换带来的开销。通常硬件MMU的辅助使得这部分开销可以忽略不计。5.4 安全与隔离配置安全不是默认开启的需要正确配置。Stage-2页表配置这是隔离的根本。确保每个Guest OS只能访问明确分配给它的内存区域和设备MMIO空间。对于共享内存要仔细配置其访问权限如只读。中断路由在vGIC中正确配置中断亲和性确保关键硬件中断如看门狗只能路由到可信的Guest如Zephyr而网络等非关键中断可以路由给Linux。设备直通的风险将物理设备直通给某个Guest后该Guest将完全控制此设备。务必确保该Guest是可信的因为一个恶意的或存在漏洞的Guest驱动可能通过DMA攻击其他Guest的内存。6. 未来展望与社区参与ZVM作为一个新生的开源项目其路线图展现出了巨大的潜力。根据项目规划除了基础功能的稳定以下几个方向值得关注更完善的设备框架支持目前设备虚拟化尤其是模拟设备的实现可能还是相对特化的。未来可能会抽象出一套标准的虚拟设备框架让开发者能像编写普通Zephyr驱动一样相对容易地为ZVM开发新的虚拟设备这将极大丰富其生态。动态资源管理当前的资源CPU、内存分配可能在启动时就静态确定了。未来的动态资源管理功能将允许在系统运行时根据负载情况在安全边界内动态调整分配给各Guest的资源配额提升系统整体资源利用率。更强大的实时性支持虽然现在已支持实时Guest但未来可能会引入更精细的实时调度策略例如支持时间敏感网络TSN所需的精确时钟同步、支持Cache分区Cache Partitioning以减少不同Guest间的缓存干扰从而将确定性提升到新的水平。与openEuler Embedded的深度整合作为openEuler社区的项目ZVM与openEuler Embedded发行版的整合将是必然。这意味着未来开发者可能通过一份标准的openEuler Embedded镜像就能获得一个预配置好ZVM和实时Guest的完整解决方案大幅降低使用门槛。对于开发者而言参与ZVM项目是一个很好的机会。你可以从以下几个方面入手测试与反馈在支持的硬件或QEMU上尝试运行报告遇到的问题。文档贡献项目的文档是开源项目的门面补充使用案例、移植指南、调试技巧等都是极有价值的贡献。代码贡献可以从修复简单的bug开始或者为你感兴趣的设备实现虚拟化驱动。生态建设尝试将ZVM与你熟悉的实时应用如ROS 2、Autoware等结合探索混合关键性系统的新应用场景。ZVM的出现为嵌入式系统架构设计打开了一扇新的大门。它让我们能够以一种更优雅、更集成的方式去构建那些既需要复杂生态又需要硬实时保障的下一代智能设备。虽然目前它还在成长初期但其背后的设计理念和务实的技术路线已经为我们勾勒出了一个清晰的、值得期待的混合关键性计算的未来。