十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在i.MX8QM上部署Xen:嵌入式虚拟化完整实践指南

在i.MX8QM上部署Xen:嵌入式虚拟化完整实践指南 如果你手里正好有一块基于i.MX8QM的模块想在上面同时跑一个带图形界面的Linux和一个响应时间严格受限的实时控制任务最顺手的方案往往不是异构核之间的共享内存通信而是直接上Xen这类虚拟机监视器。很多人对嵌入式虚拟化的第一反应是“浪费资源”但iWave这次在自家i.MX8QM模块上演示Xen其实点出了一个很实际的问题当硬件异构核不够用、任务隔离又必须做的时候虚拟化反而是成本最低的路径。有意思的是不少人在PC上遇到过类似的困惑——Docker Desktop报虚拟化未检测到实际上就是CPU的虚拟化扩展没开启或没被正确透传。嵌入式里也有这个“隐藏开关”只是它不叫BIOS而是EL2异常等级、GIC中断路由、设备树里某一行配置。任何一个环节不对Xen都起不来结果和“virtualization support not detected”没什么区别。这篇文章就借iWave的这次演示把i.MX8QM上跑Xen的完整链路拆开讲清楚适合正在评估嵌入式虚拟化方案的工程师也适合刚接触Xen ARM的开发者。1. 为什么偏偏是i.MX8QM这代处理器把虚拟化提上日程1.1 混合关键性负载带来的一芯两用需求先聊清楚一个背景为什么非得在i.MX8QM上搞虚拟化。这个SoC本身是NXP面向中高端应用处理器的产品典型目标是车载域控制器、工业HMI、医疗监护这类场景。这些场景有一个通病——同一个系统里既要有丰富的应用生态又要有确定性的实时控制。你不可能让Linux去直接管一个刹车电机但你又希望Linux和实时任务共享同一块板子、同一套电源、同一个外壳。传统的做法是物理隔离一颗芯片管Linux另一颗MCU管实时控制两片之间走SPI或UART。这种做法成熟但笨重BOM成本高、调试链路长、软件更新要跨两颗芯片同步。i.MX8QM不一样的地方在于它内部已经有2个Cortex-A72、4个Cortex-A53和2个Cortex-M4F异构核天然存在。既然有多类核心为什么不直接让A53跑Linux、A72跑实时系统呢答案是可以但现实往往做不到那么干净。1.2 异构核不是万能的A72/A53与M4的定位差异异构核方案面临两个实际问题。第一Cortex-A72和Cortex-A53之间虽然有硬件隔离但它们共享同一个DDR控制器、同一套中断控制器、同一组外设总线。一个核上恶意或意外的DMA访问完全可能踩到另一个核正在用的内存区域。i.MX8QM有Resource Domain ControllerRDC可以做一定程度的外设和内存域隔离但RDC的粒度是“外设组”和“内存区域”不是进程级的隔离配置起来也非常考验工程师对SoC手册的熟悉程度。第二M4核虽然实时性好但主频和资源有限复杂一点的电机控制算法加通信协议栈就不够用了。很多时候你需要的是一个完整的、带MMU的、能跑高算力任务的“第二系统”而不是一个小型MCU。这种场景下虚拟化就有了比较优势它能把A72或A53核心虚拟化成多个带完整操作系统的执行环境每个环境都有自己的内存视图和中断视图隔离粒度是“硬件辅助”的。1.3 硬件虚拟化扩展EL2不是摆设我在跟很多工程师聊的时候发现大家对ARM虚拟化扩展有一个误解以为Cortex-A系列芯片天生就能跑Xen/KVM。实际上ARMv8-A提供了一个关键的异常等级EL2专门给Hypervisor用。Xen在被启动时CPU必须已经处于EL2并且要接管虚拟化相关的系统寄存器。如果引导链没有做好这一步后面的所有事情都无从谈起。Cortex-A72和Cortex-A53都支持ARM虚拟化扩展包括EL2、虚拟化计时器、GIC虚拟化支持。i.MX8QM在这方面的底子是够的。iWave做的模块之所以能演示Xen是因为这个SoM把电源、时钟、DDR初始化都做完了留给软件的空间是干净的你可以把Xen作为一个独立的镜像放到启动链里不需要为“为虚拟化去改bootloader”这种脏活焦头烂额。对大多数工程师来说拿到一块人家调好的模块比拿到一堆散片自己焊底板再调启动要省下好几周时间。2. 硬件侧准备资源域划分和Xen的协作逻辑2.1 先把外设的归属问题想清楚Xen在ARM上的部署和x86上有本质区别。x86有ACPI帮你描述硬件资源虚拟化层可以相对“偷懒”ARM的世界里一切外设都是靠设备树Device Tree描述的没有设备树Xen连自己管理了哪些内存、哪些中断都不知道。所以在i.MX8QM上跑Xen第一件事不是编译镜像而是先把板级硬件资源的归属想清楚。以典型的双域配置为例Dom0管理域通常跑一个精简Linux负责启动其他虚拟机、管理驱动DomU用户域跑应用或实时系统。那么板子上的哪个UART给Dom0哪个给DomU哪个SD/eMMC控制器给Dom0做根文件系统哪个网口给DomU这些问题在写代码之前就得定下来因为它们决定了后续RDC的配置和设备树的裁剪范围。i.MX8QM的RDC机制在这个阶段很实用。RDC允许把SoC内部的外设、内存区域划分到不同的“domain”每个domain由特定的master ID访问。你可以把某个UART只分配给DominX的master那么这个外设的物理寄存器域对另一个域就是硬隔离的。Xen在ARM上负责CPU和内存的虚拟化RDC负责外设硬件隔离两者叠加隔离层才算完整。2.2 内存预留和物理地址连续性的讲究在嵌入式虚拟化里内存分配是最容易出问题的地方。Xen本身需要一片固定的物理内存作为自己的堆和数据结构Dom0需要一片连续内存跑内核每个DomU也需要各自的内存区域。普通Linux下内存分配是虚拟化的物理上碎片化无所谓但在Xen里给DomU分配内存特别要留意物理地址连续性问题——尤其是你要直通某个外设给DomU时。对于需要DMA的外设比如网络控制器或者某些高速接口它们访问的是物理地址。如果DomU的内存区域不连续DMA描述符就要做scatter/gather增加驱动复杂度。我在实际项目里的习惯是在U-Boot阶段通过bootargs把预留内存区域固定下来例如给Xen预留一块固定区域给DomU预留一块固定区域剩下的再给Dom0。这样Xen启动时不会出现“区域内地址被占用”的尴尬。i.MX8QM本身有大量DDR空间但iWave这类SoM的DDR容量一般在2GB到4GB之间。规划内存时要克制别一上来就给DomU 1GB先看看Dom0本身要跑多少应用再反过来做减法。预算算错了后面跑起来才发现内存不足就得回炉重改设备树和Xen配置相当折腾。2.3 中断路由GIC如何和Xen配合ARM平台上的中断控制器是GIC通用中断控制器i.MX8QM用的是GICv3。Xen启动后会把GIC虚拟化每个DomU都看到一套虚拟中断控制器但物理中断的分配权在Xen手里。这里有一个关键点设备直通时中断也要跟着一起直通。比如你把一个串口直通给DomU那么这个串口的SPI中断号就必须通过Xen配置映射给DomU。如果只配了设备地址、没配中断号驱动会一直轮询或者直接卡死在等待中断上表现起来就是“设备能读寄存器但数据不动”。GICv3的另一个特点是支持虚拟化扩展GICv3的VCPU接口Xen能把中断直接注入到vcpu减少了陷入开销。不过这种虚拟中断的延迟还是比物理中断要高对于微秒级实时要求的任务最理想的方式仍然是把整个物理中断直通给DomU让DomU自己处理。这部分的取舍要看你的实时域到底能容忍多少中断延迟。3. Xen启动流程从U-Boot到Dom0再到DomU的完整链路3.1 U-Boot的跳板作用i.MX8QM的标准启动链大致是片内ROM加载ATF/U-BootU-Boot加载内核镜像。在引入Xen之后这个链条多了一个环节U-Boot不再直接加载Linux而是先加载Xen然后把控制权交给Xen由Xen再启动Dom0。实际编译产物中Xen for ARM通常会被打包成一个可引导的镜像U-Boot通过bootiARM64 Image格式或者加载xen.efi如果U-Boot支持EFI的方式启动。为了确保Xen运行在EL2U-Boot需要以EL2模式启动Xen——这是U-Boot对应配置项决定的。很多第一次接触Xen on ARM的工程师会遇到“启动就崩溃”的情况查到最后往往是U-Boot把CPU降到了EL1运行XenXen一上来检查异常等级直接panic。处理办法通常是确认U-Boot编译时有CONFIG_ARM64_EL2或者类似的支持并且启动流程走的是会进入EL2路径。这类问题没有标准答案因为每个SoC的镜像加载方式不同但排查思路一致先确认跳转到Xen前后当前EL是什么。3.2 Dom0的内核既是客户机又是管理平面Xen启动后会把自己控制的设备树继承下来在这个基础上生成一份给Dom0的设备树然后启动Dom0内核。Dom0并不是一个普通的Linux它必须满足特定条件内核要编译进Xen的驱动支持也就是Xen最底层的事件通道、grant table、privcmd这些机制否则Dom0无法和Xen通信也就没法调用xl工具去创建其他虚拟机。编译Dom0内核时有几个常见选项要打开比如CONFIG_XEN_DOM0y、CONFIG_XEN_BLKDEV_BACKENDy、CONFIG_XEN_NETDEV_BACKENDy。如果你只想跑一个最小Dom0前两个可以依赖内核自带的驱动但Xen管理用的事件通道是必须的。在Xen项目文档里有专门针对ARM的Dom0内核配置建议Linaro也维护过对应的内核分支实操时直接参考比从零开始配省心。Dom0本身的资源不必给多。它只是管理平面不需要跑业务应用。我一般给Dom0分配1GB内存、2个vCPU剩下的都留给DomU。如果你把Dom0当成一个完整的Linux系统来跑GUI应用那虚拟化的性能优势会被吃掉不少因为所有DomU的I/O最终都可能经过Dom0的后端驱动。3.3 用xl和配置文件拉起第一个DomUDom0起来之后Xen的管理工具xl就可以用了。创建一个DomU的配置文件通常长这样name linux-domu kernel /xen-images/Image ramdisk /xen-images/rootfs.cpio.gz memory 1024 vcpus 2 extra consolehvc0 root/dev/ram0 rdinit/sbin/initARM上的Xen会为DomU自动生成一份基本设备树包含虚拟串口hvc0、虚拟定时器、GIC虚拟接口等。如果你的DomU只是一个普通Linux这份自动生成的设备树就够用了。跑起来之后你会在Dom0里看到xl list输出里有一个linux-domuDomU的控制台可以通过xl console linux-domu接入。第一次跑通这个流程标志着你已经在i.MX8QM上完成了最基础的虚拟化演示。很多评测机构展示“Xen虚拟化运行”其实就是跑到这一步。但实际产品要往前再走一步把某个真实外设直通给DomU或者让DomU跑一个非Linux的实时系统。4. 设备树里做文章中断、IOMMU与直通设备的配置细则4.1 设备树在整个栈里的角色嵌入式Xen的复杂度和设备树直接挂钩。x86虚拟化几乎不用关心设备树因为ACPI把硬件拓扑描述得很系统ARM则恰恰相反硬件拓扑高度碎片化设备树是唯一的硬件描述语言。Xen自身从U-Boot那里获得原始设备树然后把它“加工”成Dom0能看到的样子。如果你希望某个设备不经过虚拟化直接交给DomU使用就得在Xen的域配置里显式声明。配置直通设备在xl域配置里大致涉及这几个字段dtdev声明直通的设备树节点路径比如dtdev [smmu..., ethernet...]iomem声明需要映射给DomU的物理地址段irqs声明一并直通的中断号device_tree如果你要给DomU一份自定义设备树把路径写进去举个例子如果要把i.MX8QM上的某个MMC控制器直通给DomU除了在xl配置里声明设备节点地址和中断号你还需要给DomU准备一份单独的设备树只保留这个MMC控制器、GIC、定时器、串口等必要节点。否则DomU内核启动时会枚举设备树里不存在的设备或者试图初始化一个没有对应内存映射的设备导致驱动加载失败。4.2 直通设备的DMA问题是最大的坑直通外设看似简单难点在DMA。当一个外设被直通给DomU时外设做的每一次DMA读写的目标物理地址必须是DomU真实拥有的物理地址。可问题在于Xen给DomU分配内存时DomU看到的“物理地址”是经过Xen伪装的真实物理地址可能完全不连续或者地址不同。处理这个问题的标准答案是IOMMU。i.MX8QM内部有System MMU组件Linux侧对应ARM SMMU驱动。当IOMMU启用时DomU里的设备驱动做DMA时实际地址转换由IOMMU完成外设看到的地址既可以是真实的物理地址也可以是由IOMMU映射出来的地址隔离性更强。但是IOMMU不是默认就生效的。很多SoC默认情况下外设DMA是“绕过IOMMU的”这时候如果直通设备给DomU设备驱动在DomU里编组DMA描述符时拿到的地址是DomU的物理地址而这个地址在真实硬件上不存在DMA就会写到错误的地方轻则数据错乱重则改写其他Domain的内存。所以凡是做设备直通的项目我都会建议先确认IOMMU是否在系统里被正确使能具体就是看设备树里SMMU节点是否为status okay以及Dom0内核是否开启了对应的SMMU驱动。这个坑是性能之外的“安全红线”宁可禁止直通也不能冒着DMA踩踏的风险硬上。4.3 给DomU裁剪设备树的通用做法给DomU做设备树是一项需要耐心的工作但也有一些固定的套路。最省事的方法是从Xen生成的Dom0设备树里把不需要的节点全部删除只保留CPU、内存、GIC、定时器、串口和你要直通的那几个外设节点。用dtc工具可以将DTB反编译成DTS文本改完之后再编译回DTB操作不复杂。一个标准的DomU DTS骨架大致是这样/dts-v1/; / { compatible xen,pvh; #address-cells 0x2; #size-cells 0x2; cpus { #address-cells 0x1; #size-cells 0x0; cpu0 { device_type cpu; compatible arm,cortex-a53; reg 0x0; }; }; memory0 { device_type memory; reg 0x0 0x0 0x0 0x40000000; }; gic { compatible arm,gic-v3; reg 0x0 0x51a00000 0x0 0x10000; interrupt-controller; #interrupt-cells 0x3; }; timer { compatible arm,armv8-timer; interrupts 0x1 0xd 0xf04; }; chosen { bootargs consolehvc0; }; };注意compatible xen,pvh要保留这告诉Linux内核当前运行在Xen环境里内核会优先走Xen PV协议进行初始化而不是试图直接操作裸机硬件。如果你忘了这个字段内核可能按普通模式启动然后卡在找不到根设备的窘境里。5. 多虚拟机跑起来的实际表现性能开销和实时性数据5.1 计算密集型的损耗主要在哪很多人关心虚拟化到底慢多少。以我在ARM平台上的实测经验纯CPU密集型负载在Xen下的损耗非常小一般不超过1%到2%。这得益于现代ARM核的虚拟化扩展做得比较彻底普通指令在EL1照常执行只有特权操作才会陷入到EL2的Hypervisor里。真正的损耗出现在两类操作上一是系统调用和上下文切换密集的负载二是I/O路径。系统调用本身不算是虚拟化的开销来源但每次虚拟机之间切换比如从DomU切换到Dom0处理某个请求都会涉及异常级别切换TLB和缓存也要注意刷新。某些benchmark跑出来差10%以上基本都是这种场景。I/O开销则是另一回事网络包每个都要经过前后端驱动多次内存拷贝这个是传统半虚拟化的固有成本。所以在选择哪些负载放DomU、哪些放裸机时我的原则是算力密集的放DomU没关系I/O密集的尽量直通物理设备或者确保网络路径足够精简不要反复跨域。5.2 网络与块设备虚拟化开销网络和磁盘这类块设备在Xen里通常用半虚拟化方案DomU里有一个前端口驱动比如网络上是netfrontDom0里有一个后端驱动netback两者通过共享内存和事件通道通信。好处是不挑具体硬件任何网络控制器都能用坏处是路径长CPU占用率高。如果你只是做一个HMI系统网络流量不大PV网络完全够用。但如果你要跑数据吞吐很高的业务比如实时视频流那就需要考虑把网卡直通给DomU让DomU的驱动直接操作物理网卡。这样网络栈不经过Dom0延迟和吞吐都有较大改善。不过代价是这时候你需要像前面说的那样处理IOMMU、中断直通、设备树裁剪一系列工作。块设备也是一样。用PV块设备时DomU的磁盘读写都要请求Dom0的后端驱动去访问真实磁盘中间经过多次内存映射。对启动时间敏感的实时系统我建议把内核和根文件系统做成initramfs一次读进内存减少运行时的磁盘依赖。5.3 实时域需要注意的调度细节在i.MX8QM这种多核平台上DomU的vCPU数量可以超过物理核数但不代表你该这么做。对于实时工作负载强烈建议把实时DomU的vCPU绑定到固定的物理核心上在Xen的配置里用cpus字段指定cpu affinity避免vCPU在不同核之间漂移。Xen的调度器在ARM上有多种选择比如credit2和null调度器。null调度器的思路是每个vCPU固定绑定一个物理CPU减少迁移和锁竞争适用于虚拟化层不做复杂调度的场景对实时性更友好。虽然null调度器在一些负载下可能浪费空闲核心但如果你只需要“一个域独占一个核”它是最简单的选择。另外还要注意物理核的类型差异。i.MX8QM有A72和A53两种核性能不同。你把实时DomU绑到A72上自然比绑到A53上获得更高性能但代价是A72功耗更高。很多时候产品定义里“高性能核心给实时任务”还是“给Linux业务”是一个需要和系统功耗模型一起权衡的问题。6. 部署时最容易翻车的几个地方与排查建议6.1 Dom0起不来先查这三项Dom0无法启动是部署Xen时最常见的第一道坎。排查顺序我习惯固定为三步。第一确认Xen是否真的接管了硬件。看启动日志里有没有Xen的内存布局打印如果连Xen自己都卡住问题大概率在U-Boot加载方式或EL2切换上。第二确认Dom0内核有没有正确编译Xen支持。没有CONFIG_XEN_DOM0y内核会把自己当成裸机Linux启动到一半找不到某些硬件就panic。这种问题的特征是Xen已经起来日志能看到Xen版本和内存信息但下面的Dom0输出戛然而止。第三确认设备树。Xen会基于自己收到的DTB生成Dom0的DTB如果U-Boot传给Xen的DTB本身是不完整的或者Xen不认识某些节点Dom0可能拿不到正确的中断和内存信息。排查时可以在Xen启动日志里观察它是否报错过某些节点无法识别。这三步走完能解决九成以上的Dom0启动问题。6.2 DomU分配了内存却一帧都得不到的怪异问题另一个常见的坑是DomU看起来启动成功了内存也分配了但设备驱动获取不到任何数据比如网络通了但丢包率100%或者GPU设备初始化失败。碰到这种情况优先怀疑DMA和中断的映射没有配对。芯片里的设备寄存器地址、中断号、IOMMU映射这三个要素必须同时成立设备才会工作。我在一个项目里遇到过网卡直通给DomU后收包正常但发包完全不动的现象。查了三天最后发现是中断号在设备树里写错了驱动和中断线对不上发出去的包因为没有中断确认而被软件栈一直堆积。所以排查这类问题时别急着怀疑Xen配置先把设备树反编译出来对照SoC手册确认设备节点里的reg和interrupts字段是否精确无误。还有一个容易被忽略的细节某些外设的时钟和电源在i.MX8QM里由系统控制器固件SCFW管理。如果你直通的设备没有在对应的域里配置时钟权限DomU访问设备寄存器时可能会读回全零或触发总线错误。这种问题最迷惑人因为寄存器地址看着没问题但设备就是不响应。解决方式是在SoC侧的配置里把对应设备的时钟和电源通道授权给DomU所在的域。6.3 关于虚拟化支持检测这个经典误解最后说一个和很多人在Docker里遇到的“virtualization support not detected”类似的经典误解。在PC上这个问题通常是BIOS里VT-x/AMD-V没打开在i.MX8QM这类嵌入式平台上Xen能否检测到虚拟化支持取决于三件看似不起眼的事CPU是否从EL2进入Xen、GIC虚拟化扩展是否被Xen正确初始化、以及设备树里的enable-method和CPU节点是否合法。如果这三者有一项不对Xen的表现可能是启动到一半静默退出或者Dom0里看不到任何虚拟化扩展信息。很多人这时候会怀疑SoC“不支持虚拟化”实际上芯片本身完全支持只是引导方式没走对。所以我的建议是遇到类似问题时先别急着下“硬件不支持”的结论把启动日志从U-Boot开始完整抓一遍重点看异常等级切换和Xen的内存初始化输出。6.4 我自己的一套快速验证顺序如果在i.MX8QM上从头评估Xen我一般不会直接上完整业务系统而是先做一个最简单的最小化验证Dom0起来之后用xl创建一个只挂载虚拟控制台的最小DomU确认控制台能输入输出。这一步通过说明Xen的CPU虚拟化、中断虚拟化、事件通道机制都工作正常。接下来再做第二层验证给DomU分配一段较大的内存并用xl debug-keys观察Xen内存使用情况确认没有内存泄漏或映射异常。前两步都稳定了才去考虑设备直通、IOMMU和实时性调优这些进阶操作。这套顺序看起来保守但能帮你把虚拟化问题从业务问题里剥离出来。否则一旦你把真实业务和虚拟化混在一起排查任何一个环节出错都会让人抓狂。iWave这次演示的价值就在于此——它给后来的人划了一条已经跑通的路剩下的就是在熟悉路线的基础上按自己的业务需求往上叠东西。
返回列表