基于Yocto与KVM在ARM平台构建嵌入式虚拟化系统实践

发布时间:2026/6/25 18:01:06
基于Yocto与KVM在ARM平台构建嵌入式虚拟化系统实践 1. 项目概述与核心价值在嵌入式系统开发尤其是像NXP QorIQ LS系列这样的高性能多核ARM平台上虚拟化技术正从一个“锦上添花”的特性转变为一种刚需。你可能遇到过这样的场景需要在一套硬件上同时运行一个实时操作系统和一个富功能的Linux系统或者希望将不同的功能模块隔离在不同的虚拟机中以增强安全性又或者只是想在一个物理板上快速搭建多个独立的测试环境。传统的多系统启动切换不仅耗时而且无法实现真正的并行与隔离。这时KVMKernel-based Virtual Machine的价值就凸显出来了。它不是一个独立的用户态程序而是直接构建在Linux内核中的虚拟化基础设施。简单来说KVM把Linux内核本身变成了一个超级管理程序Hypervisor。它利用现代CPU如ARM的虚拟化扩展提供的硬件辅助虚拟化能力让虚拟机Guest的指令可以直接在物理CPU上执行从而获得接近原生硬件的性能。而QEMU则扮演了“硬件模拟器”和“虚拟机管理器”的角色负责为虚拟机创建虚拟的CPU、内存、磁盘和网络设备。那么为什么要在Yocto项目里折腾KVM和QEMU呢因为Yocto是嵌入式Linux定制化的“瑞士军刀”。通过Yocto我们可以从源码开始构建一个完全为我们的目标硬件和虚拟化需求定制的Linux发行版。这包括了定制内核精确地启用KVM模块、虚拟网络驱动如TAP/TUN、桥接、virtio半虚拟化驱动等移除所有不必要的模块让内核更精简、更高效。集成工具链将编译好的QEMU、必要的库文件如libfdt, glib直接打包进根文件系统开箱即用。统一管理内核、根文件系统、应用程序的版本和配置通过一套bitbake配方recipes统一管理确保整个软件栈的一致性极大简化了部署和复现的复杂度。本文将以NXP QorIQ LS1046A等ARM平台为背景手把手带你走通从零开始在Yocto环境中构建支持KVM虚拟化的完整系统并成功启动一个Linux虚拟机的全过程。无论你是嵌入式软件工程师、系统架构师还是对底层虚拟化技术感兴趣的开发者这篇指南都将提供从原理到实践的详尽参考。2. 环境准备与Yocto项目初始化在开始配置和构建之前一个正确配置的Yocto构建环境是基石。这里假设你已经在开发主机通常是x86_64的Linux系统上完成了基础的Yocto环境搭建。我们以NXP官方提供的meta-freescale层为例目标是构建一个支持ls1046ardb机器的系统。2.1 获取源码与设置构建目录首先你需要获取Yocto Project的核心组件Poky和NXP的BSP层。# 1. 创建项目目录并进入 mkdir ~/yocto-kvm cd ~/yocto-kvm # 2. 克隆PokyYocto Project参考发行版 git clone -b kirkstone https://git.yoctoproject.org/poky.git # 注意请根据NXP BSP文档推荐的分支选择Poky版本此处以kirkstone为例。 # 3. 克隆NXP的BSP元数据层 git clone -b kirkstone https://github.com/nxp-imx/meta-freescale.git # 4. 初始化构建环境 source poky/oe-init-build-env build执行完source命令后你的终端会切换到build目录下这是你的主要工作目录。2.2 配置local.conf文件build/conf/local.conf文件是控制本次构建的核心配置文件。我们需要针对虚拟化构建进行一些关键调整。# 使用编辑器打开 local.conf vi conf/local.conf找到并修改或添加以下行# 1. 指定目标机器这里以LS1046A RDB板为例 MACHINE ?? ls1046ardb # 2. 关键选择支持虚拟化的镜像类型作为构建目标。 # fsl-image-virt 是NXP预配置的、包含QEMU和Guest根文件系统的镜像。 # 如果你想从一个更基础的镜像开始可以先用 fsl-image-core后续再手动添加组件。 IMAGE_FSTYPES tar.gz ext4 # 设置我们要构建的镜像名称 IMAGE_CLASSES image_types_fsl IMAGE fsl-image-virt # 3. 为RootFS增加额外的软件包。 # 即使使用fsl-image-virt确保QEMU被包含也是好习惯。 IMAGE_INSTALL:append qemu # 4. 性能优化启用对Hugepages大页的支持。 # Hugepages能显著提升虚拟机内存访问性能后续运行QEMU时会用到。 KERNEL_FEATURES:append features/hugepages/hugepages-1G.scc features/hugepages/hugepages-2M.scc DISTRO_FEATURES:append hugepages # 5. 根据你的开发主机性能调整并行构建线程数和IO线程数以加快构建速度。 # BB_NUMBER_THREADS 通常设置为你的CPU核心数PARALLEL_MAKE 设置为核心数或核心数*1.5。 BB_NUMBER_THREADS ? 8 PARALLEL_MAKE ? -j 8注意fsl-image-virt镜像已经默认包含了QEMU、一个供Guest使用的精简根文件系统fsl-image-core以及对应的设备树。对于初次尝试和快速验证这是最便捷的路径。如果你想更精细地控制Host和Guest的系统组成可以选择从fsl-image-core开始然后手动添加所有组件这会在后续章节详细说明。3. 内核配置为KVM虚拟化启用关键选项内核是虚拟化能力的核心。Yocto通过bitbake virtual/kernel来构建内核并通过menuconfig进行交互式配置。我们的目标是配置一个既能作为Host宿主机运行KVM又能作为Guest虚拟机内核的通用镜像。3.1 启动内核配置菜单首先确保你位于Yocto的build目录下。# 清理之前的编译结果确保从干净状态开始配置非必须但推荐 bitbake -c clean virtual/kernel # 启动Linux内核的menuconfig配置界面 bitbake -c menuconfig virtual/kernel执行上述命令后会弹出一个基于ncurses的文本图形配置界面。如果你的终端不支持可能需要设置TERM变量或使用支持X11转发的SSH客户端。3.2 逐项启用虚拟化相关配置以下配置路径和选项名称基于较新的Linux内核版本5.x。不同版本路径可能略有差异但关键选项名称通常保持一致。请按照以下顺序在菜单中查找并启用。3.2.1 启用虚拟化框架这是最顶层的开关。进入主菜单找到Virtualization选项。按Y键将其编译进内核[*]而不仅仅是模块[M]。对于嵌入式系统我们通常选择直接编译进内核避免模块加载的复杂性。[*] Virtualization ---进入Virtualization子菜单后启用KVM支持[*] Kernel-based Virtual Machine (KVM) support这个选项是KVM架构的核心它会在内核中注册一个字符设备通常是/dev/kvm供QEMU等用户态工具调用以创建和管理虚拟机。3.2.2 配置Host端虚拟网络支持为了让虚拟机能够访问外部网络我们需要在Host内核中启用网络桥接和TUN/TAP设备支持。启用802.1d以太网桥接退回主菜单进入Networking support-Networking options。找到并启用* 802.1d Ethernet Bridging。这允许Host创建一个虚拟交换机网桥将物理网卡和虚拟机的TAP网卡连接在一起。启用通用TUN/TAP设备驱动退回主菜单进入Device Drivers-Network device support-Universal TUN/TAP device driver support。将其设置为*编译进内核。TUN/TAP是虚拟网络设备TAP模拟了以太网设备QEMU会为每个虚拟机创建一个TAP设备并将其连接到上述的网桥上。启用vhost-net内核加速强烈推荐退回主菜单再次进入Virtualization子菜单。找到并启用* Host kernel accelerator for virtio net(可能也叫做VHOST_NET)。为什么需要这个默认情况下虚拟机网络数据包的处理virtio后端在QEMU进程的用户空间进行这涉及多次上下文切换和内存拷贝。vhost-net将这个数据平面data plane移到内核空间让内核线程直接处理virtio网络请求能大幅降低延迟、提高吞吐量是性能关键路径上的必选项。3.2.3 配置Guest端虚拟I/O驱动 (virtio)virtio是一种半虚拟化I/O框架它定义了一套高效的、用于虚拟机和宿主机之间通信的通用接口。相比完全模拟的硬件如e1000网卡virtio设备性能高得多。启用virtio PCI传输层进入Device Drivers-Virtio drivers。启用* PCI driver for virtio devices。即使我们的ARM平台可能不使用标准的PCIe总线QEMU的virt机器模型也会通过一个虚拟的PCI总线来呈现virtio设备因此这个驱动是必需的。启用virtio块设备驱动虚拟磁盘进入Device Drivers-Block devices。确保Block devices顶层是[*]启用状态然后找到并启用* Virtio block driver。这样Guest系统才能识别并使用由QEMU提供的虚拟硬盘。启用virtio网络设备驱动虚拟网卡进入Device Drivers-Network device support。找到并启用* Virtio network driver。这样Guest系统才能识别并使用由QEMU和vhost-net提供的虚拟网卡。3.2.4 启用大页文件系统支持进入File Systems-Pseudo filesystems。启用[*] Huge TLB file system support。作用这允许我们在用户空间挂载hugetlbfs。QEMU可以通过-mem-path参数从这个文件系统分配大块、连续的物理内存给虚拟机避免了大量4KB小页带来的TLB转址旁路缓存压力对虚拟机内存访问性能有显著提升尤其是内存密集型应用。3.2.5 启用Guest控制台支持QEMU的virt机器模型为ARM架构模拟了PL011UART。进入Device Drivers-Character devices-Serial drivers。启用* ARM AMBA PL011 serial port support。同时启用其下的[*] Support for console on AMBA serial port。这样内核才能将早期控制台信息输出到这个虚拟串口我们才能通过QEMU的-serial参数捕获到Guest内核的启动日志和Shell。3.2.6 保存与退出配置完成后按左右方向键选择 Save 直接按回车使用默认的配置文件路径通常是.config然后选择 Exit 直到退出menuconfig。3.3 内核配置的验证与构建退出menuconfig后Yocto会自动将新的配置保存到内核构建目录。接下来我们需要基于这个配置重新构建内核。# 强制重新编译内核因为配置已更改 bitbake -c compile -f virtual/kernel # 然后重新构建内核映像包 bitbake virtual/kernel构建完成后你可以在tmp/deploy/images/MACHINE/目录下找到新生成的内核映像如Image或zImage和内核设备树 blob.dtb文件。实操心得在menuconfig中使用/键可以搜索配置选项非常方便。例如输入/KVM可以快速定位到KVM相关的配置项。另外如果你不确定某个选项的作用可以按?键查看详细的帮助文本。4. 构建与集成QEMU及Guest根文件系统现在我们有了一个支持KVM的内核。接下来需要构建用户态的虚拟机管理器QEMU以及准备一个供虚拟机内部运行的Guest根文件系统。4.1 构建QEMU如果你在local.conf中设置了IMAGE_INSTALL:append qemu并且构建的镜像是fsl-image-virt那么QEMU应该已经在构建镜像的过程中被自动编译并打包进去了。你也可以单独编译QEMU包# 清理并重新构建qemu bitbake -c cleansstate qemu bitbake qemu构建完成后QEMU的可执行文件qemu-system-aarch64用于ARMv8qemu-system-arm用于ARMv7会被安装到目标根文件系统的/usr/bin/目录下。4.2 准备Guest根文件系统对于fsl-image-virtYocto已经为我们创建好了一个基于fsl-image-core的Guest根文件系统并压缩为fsl-image-core-MACHINE.ext2.gz放在了Host根文件系统的/boot目录下。这是一种非常便捷的“镜像套镜像”方式。但是如果你想使用一个更定制化或更精简的Guest系统例如fsl-image-minimal或者你的Host镜像是fsl-image-core则需要手动完成Guest根文件系统的集成。这里介绍手动集成的通用方法它利用了Yocto的merge-files功能。4.2.1 构建Guest根文件系统假设我们为Guest构建一个最小的系统bitbake fsl-image-minimal构建完成后Guest的根文件系统压缩包位于tmp/deploy/images/MACHINE/fsl-image-minimal-MACHINE.ext2.gz。4.2.2 使用merge-files集成到Host镜像merge-files是一个Yocto的扩展允许你将任意文件或目录“合并”到最终构建的根文件系统镜像中。创建merge目录结构# 在Yocto源码层目录下创建merge目录 mkdir -p ../meta-freescale/recipes-extended/merge-files/merge-files/merge/boot/注意路径meta-freescale/recipes-extended/merge-files/merge-files/merge/是merge-files配方约定的固定位置。放在此目录下的所有内容在构建根文件系统时会被复制到目标根文件系统的根目录/下。复制Guest根文件系统# 将构建好的Guest根文件系统复制到merge目录下的boot中 cp tmp/deploy/images/ls1046ardb/fsl-image-minimal-ls1046ardb.ext2.gz ../meta-freescale/recipes-extended/merge-files/merge-files/merge/boot/guest-rootfs.ext2.gz你也可以在这里复制Guest内核映像如果你为Guest准备了不同的内核。触发merge并重新构建Host镜像# 首先强制重新安装merge-files确保文件被复制 bitbake -c install -f merge-files # 然后重新构建你的Host镜像例如fsl-image-core bitbake fsl-image-core重新构建后你的Host根文件系统的/boot目录下就会包含guest-rootfs.ext2.gz这个文件了。4.3 生成最终系统镜像完成所有配置和构建后生成最终的SD卡或Flash镜像bitbake fsl-image-virt # 或者 bitbake fsl-image-core (如果你用的是core)最终的镜像文件如.wic或.sdcard会出现在tmp/deploy/images/MACHINE/目录下。将其烧录到开发板的存储设备中。5. 在目标板上运行KVM虚拟机将烧录好的镜像启动到开发板上通过串口或SSH登录到Host系统。现在我们来到了最激动人心的环节——启动第一个虚拟机。5.1 启动前的准备工作5.1.1 配置大页内存Hugetlbfs为了获得最佳性能我们使用大页内存为虚拟机分配内存。挂载hugetlbfs# 创建挂载点 mkdir -p /mnt/hugepages # 挂载2MB大小的大页文件系统 mount -t hugetlbfs nodev /mnt/hugepages -o pagesize2M对于ARMv8平台如果CPU支持1GB大页也可以挂载pagesize1G。预留大页内存# 查看当前大页状态 cat /proc/meminfo | grep Huge # 预留256个大页每个2MB总计512MB echo 256 /proc/sys/vm/nr_hugepages # 再次检查确认HugePages_Total变为256 cat /proc/meminfo | grep HugePages_Total这个操作从系统可用内存中划出512MB的连续物理内存专供大页分配使用。5.1.2 准备网络桥接可选但推荐为了让虚拟机获得和Host同等的网络访问能力我们需要创建一个网桥。# 安装桥接工具如果Host系统未包含 # 在Yocto中可以提前将 bridge-utils 添加到 IMAGE_INSTALL_append # 假设Host的以太网接口是 eth0 BRIDGE_IFbr0 PHYSICAL_IFeth0 # 创建网桥 ip link add name $BRIDGE_IF type bridge ip link set $BRIDGE_IF up # 将物理网卡加入网桥并清空其IPIP由网桥接管 ip addr flush dev $PHYSICAL_IF ip link set $PHYSICAL_IF master $BRIDGE_IF ip link set $PHYSICAL_IF up # 为网桥配置IP地址可以从DHCP获取或静态配置 udhcpc -i $BRIDGE_IF # 或者静态配置 ip addr add 192.168.1.100/24 dev $BRIDGE_IF同时你需要准备一个QEMU网络启动脚本例如/root/qemu-ifup#!/bin/sh # /root/qemu-ifup switchbr0 # 与你创建的网桥名一致 if [ -n $1 ]; then ip link set $1 up sleep 1 brctl addif $switch $1 exit 0 else echo Error: no interface name provided. exit 1 fi赋予脚本执行权限chmod x /root/qemu-ifup。QEMU在启动TAP设备时会自动调用此脚本将TAP设备加入网桥。5.2 启动QEMU-KVM虚拟机一切就绪后使用一个复杂的QEMU命令行来启动虚拟机。下面是一个针对ARMv8AArch64平台的完整示例命令我们将其拆解说明/usr/bin/qemu-system-aarch64 \ -enable-kvm \ # 启用KVM硬件加速这是性能关键 -machine typevirt \ # 指定机器类型为 virt这是QEMU为虚拟化优化的通用ARM机器模型 -cpu host \ # 将虚拟CPU模型设置为与宿主机CPU一致最大化性能兼容性 -smp 2 \ # 为虚拟机分配2个虚拟CPU核心 -m 512M \ # 为虚拟机分配512MB内存 -mem-path /mnt/hugepages \ # 从大页文件系统分配内存提升性能 -kernel /boot/Image \ # Guest内核镜像路径与Host内核可以是同一个 -initrd /boot/guest-rootfs.ext2.gz \ # Guest根文件系统initramfs格式 -append root/dev/ram0 rw consolettyAMA0 earlyprintk \ # Guest内核启动参数 -nographic \ # 禁用图形输出所有输出重定向到串口 -serial mon:stdio \ # 将Guest串口0重定向到当前标准输入输出 -netdev tap,idnet0,script/root/qemu-ifup,downscriptno \ # 网络后端TAP设备使用上述脚本 -device virtio-net-pci,netdevnet0 \ # 网络前端virtio网卡设备连接到net0后端 -monitor telnet:127.0.0.1:4444,server,nowait \ # 开启QEMU Monitor监听本地4444端口 -daemonize # 让QEMU在后台运行参数详解与避坑指南-enable-kvm这是灵魂参数。没有它QEMU将进行纯软件模拟速度极慢。加上它QEMU会通过/dev/kvm接口调用KVM内核模块让虚拟机指令直接在物理CPU上执行。-machine virtvirt机器模型是专门为虚拟化设计的它提供了virtio-mmio、GPIO等虚拟设备是运行Linux Guest的最佳选择。-cpu host直接暴露宿主机的CPU特性给虚拟机避免了复杂的CPU模拟性能最好。对于需要严格模拟特定CPU型号的场景可以换成-cpu cortex-a72等。-initrd与-append这里我们使用initrd方式启动。root/dev/ram0告诉内核从initrd即我们传入的压缩根文件系统启动。这是一种简单快速的启动方式适合调试。对于生产环境你可能需要虚拟硬盘-drive参数和更复杂的根文件系统。-serial mon:stdio这是一个非常实用的技巧。它将Guest的串口输出和QEMU Monitor的输入混合到当前终端。你可以按CtrlA C来在串口控制台和Monitor之间切换。-monitor telnet:...即使让QEMU后台运行我们仍然可以通过telnet 127.0.0.1 4444连接到Monitor进行虚拟机状态查看、关机等操作。-daemonize让QEMU在后台运行释放当前终端。针对ARMv732位平台命令主要区别在于QEMU可执行文件和内核格式/usr/bin/qemu-system-arm \ -enable-kvm \ -machine typevirt \ -cpu host \ -smp 2 \ -m 512M \ -mem-path /mnt/hugepages \ -kernel /boot/zImage \ # ARMv7通常使用zImage格式内核 ... # 其余参数与上述相同执行命令后如果一切正常你将看到Guest内核的启动日志滚动输出最终进入Guest系统的登录提示符。恭喜你你的第一个基于Yocto和KVM的ARM虚拟机已经成功运行6. 高级配置与调试技巧成功启动虚拟机只是第一步。在实际开发和部署中你会遇到各种需要深入配置和调试的情况。6.1 QEMU Monitor的妙用QEMU Monitor是一个强大的管理界面。如果你在启动时指定了-monitor telnet:...可以通过telnet连接telnet 127.0.0.1 4444连接成功后会看到(qemu)提示符。常用命令有info cpus查看所有虚拟CPU的状态和对应的Host线程ID。info registers查看当前虚拟CPU的寄存器值。system_reset软重启虚拟机。stop/cont暂停和继续虚拟机执行。savevm tag/loadvm tag保存和加载虚拟机快照需要配置虚拟磁盘。quit停止并退出QEMU。6.2 使用GDB调试Guest内核对于内核开发者调试Guest内核是常见需求。QEMU内置了GDB Stub。启动QEMU时添加GDB调试参数/usr/bin/qemu-system-aarch64 \ ... \ -S \ # 启动后暂停CPU等待GDB连接 -gdb tcp::1234 \ # 在1234端口监听GDB连接 ... # 不要加 -daemonize在Host上使用交叉编译工具链中的GDB连接# 假设你的交叉编译工具链前缀是 aarch64-poky-linux- aarch64-poky-linux-gdb /path/to/your/guest/vmlinux (gdb) target remote :1234 (gdb) break start_kernel # 在内核入口处设置断点 (gdb) continue # 继续执行命中断点现在你就可以像调试普通程序一样单步执行、查看变量、回溯堆栈来调试Guest内核了。6.3 性能调优与资源隔离CPU亲和性pinning为了避免虚拟机vCPU线程在物理CPU核心间频繁迁移缓存失效可以将vCPU线程绑定到特定的物理核心上。首先通过QEMU Monitor的info cpus找到vCPU线程ID然后使用taskset命令# 假设vCPU0的线程ID是1984将其绑定到物理CPU核心0 taskset -pc 0 1984CPU隔离isolcpus在Host内核启动参数中在U-Boot中设置bootargs加入isolcpus1,2可以将物理CPU核心1和2从Linux通用调度器中隔离出来专供虚拟机使用减少调度干扰。cgroups限制使用cgroups可以限制虚拟机进程的资源使用如CPU份额、内存上限等。# 创建一个cgroup mkdir /sys/fs/cgroup/cpu/qemu mkdir /sys/fs/cgroup/memory/qemu # 将QEMU进程PID加入该cgroup echo qemu_pid /sys/fs/cgroup/cpu/qemu/cgroup.procs echo qemu_pid /sys/fs/cgroup/memory/qemu/cgroup.procs # 设置CPU份额为512默认是1024即最多占用50%的CPU时间 echo 512 /sys/fs/cgroup/cpu/qemu/cpu.shares # 设置内存上限为1GB echo 1G /sys/fs/cgroup/memory/qemu/memory.limit_in_bytes6.4 常见问题排查实录即使按照指南操作你也可能会遇到一些问题。这里记录几个我踩过的坑和解决方法QEMU启动失败报错Could not access KVM kernel module: No such file or directory原因Host内核没有加载KVM内核模块或者/dev/kvm设备节点不存在。排查lsmod | grep kvm检查模块是否加载。对于ARM模块名通常是kvm。如果没有尝试modprobe kvm。检查/dev/kvm是否存在权限是否为crw-rw-rw-。如果没有可能是内核编译时KVM没有编入应为[*]而非[M]需要重新配置编译内核。Guest内核启动后卡住无输出或报错virtio_mmio: probe of virtio-mmio.0 failed with error -16原因Guest内核缺少必要的驱动或内核命令行参数不对。排查确保Guest内核配置了前文所述的所有virtio驱动块设备、网络、PCI以及PL011串口控制台。检查-append参数中的console设置是否正确指向了QEMU模拟的串口通常是ttyAMA0。尝试在Guest内核命令行添加earlycon和ignore_loglevel参数如earlyconpl011,0x09000000 ignore_loglevel以输出更早的日志。虚拟机网络不通原因网桥配置错误、TAP设备未创建或qemu-ifup脚本有问题。排查在Host上执行ip link show检查br0网桥和tap0设备是否存在且状态为UP。检查tap0是否在br0的成员列表中bridge link show。在Guest内部使用ip addr检查网卡是否获得IP地址可能需要运行udhcpc或配置静态IP。检查Host的防火墙和iptables规则确保没有阻止桥接流量。使用大页内存启动QEMU失败报错failed to allocate memory原因大页内存未预留或挂载点权限不对。排查确认/proc/sys/vm/nr_hugepages值大于0。确认/mnt/hugepages挂载成功mount | grep huge。检查QEMU进程用户通常是root是否有权访问挂载点。虚拟机性能异常缓慢原因没有使用-enable-kvm或者使用了纯软件模拟的CPU型号。排查务必确认QEMU命令行中有-enable-kvm和-cpu host。可以通过在Guest内运行dmidecode或lscpu查看虚拟的CPU型号。通过这套基于Yocto的构建和部署流程你获得的不只是一个能运行的虚拟化环境更是一个高度定制化、可复现的嵌入式软件栈。从内核驱动的细微调整到根文件系统的精简直至单个软件包Yocto赋予了你对整个系统无与伦比的控制力。结合KVM接近原生的性能你可以在真实的嵌入式硬件上构建出灵活、高效且隔离性强的多系统解决方案无论是用于产品开发、持续集成测试还是作为复杂的边缘计算节点这套技术组合都提供了坚实可靠的基础。