RK3588上搭建KVM虚拟机:arm64虚拟化环境与性能优化实战

发布时间:2026/9/24 12:19:47
RK3588上搭建KVM虚拟机:arm64虚拟化环境与性能优化实战 1. 为什么要在RK3588上折腾KVM虚拟机1.1 RK3588的虚拟化底子到底行不行RK3588这块芯片出来之后我在好几个项目里都用过它。8核ARM架构4个Cortex-A76大核加4个Cortex-A55小核、双千兆网口、PCIe接口齐全再加上最高32GB内存的支持这几项叠加起来已经不能把它简单当成“开发板”看了。很多团队直接拿它做边缘服务器、NAS、小型CI编译节点甚至在工控场景里顶替原来的x86小主机。KVMKernel-based Virtual Machine在ARM64上的成熟度其实远超很多人想象。ARMv8-A架构从设计之初就带了虚拟化扩展在CPU里增加了EL2异常级别专门给Hypervisor使用。KVM在arm64上就是跑在EL2这一层Guest操作系统运行在EL1虚拟机里的应用程序运行在EL0。Cortex-A76和Cortex-A55都完整支持这套虚拟化扩展所以RK3588在硬件层面完全具备跑KVM的条件。真正让我在这类板子上认真考虑KVM的是“一台设备同时跑多个完整系统”的需求。嵌入式项目里经常要同时验证不同内核版本、不同发行版、不同编译工具链的环境如果每套环境都单独准备一块板子成本和理线都是灾难。在RK3588上做虚拟化等于把一台物理设备拆成多个隔离环境每个环境拥有独立内核、独立文件系统互相之间不干扰。1.2 KVM、Docker、纯QEMU到底怎么选三种方案的定位完全不同我在选型时把这几点理清楚之后后面就少走了很多弯路。Docker的思路是共享宿主机内核通过namespace和cgroup做隔离。优点在于资源开销小、启动快秒级拉起一个容器缺点也很明显容器里的进程和宿主机共享同一个内核你不可能在Docker里跑一个和宿主机不同内核版本的系统更不可能在Docker里做内核调试。如果只是跑应用、跑服务Docker完全够用但如果要测内核、要跑不同发行版、要隔离恶意样本Docker就不合适了。纯QEMU即TCG软件仿真可以跨架构模拟比如在x86上模拟ARM系统或者在ARM上模拟x86。这种方式的优点是无敌灵活缺点是性能损失太大因为所有指令都要经过二进制翻译根本不具备生产价值。我在调试启动流程的时候才会用TCG平时不会把它当作正经运行环境。KVM走的是硬件虚拟化路径Guest指令直接跑在物理CPU上只有特权操作需要陷入Hypervisor处理。配合virtio系列半虚拟化设备磁盘、网络的性能损耗可以控制在很小的范围内。更重要的是KVM是内核级隔离Guest崩溃不会拖垮宿主机安全边界清楚。对比项KVMDocker纯QEMU(TCG)隔离级别硬件级虚拟化独立内核进程级隔离共享内核指令模拟独立内核能否运行异构架构同架构下可以ARM上跑ARM不支持支持跨架构性能损耗低接近裸机极低高通常不能用启动速度秒级到分钟级毫秒级慢典型场景服务器虚拟化、内核测试、多系统微服务、应用打包交叉调试、学习模拟RK3588上有8个CPU核心跑一个4 vCPU的虚拟机做编译任务宿主机还能剩下4个核心做其他事情。这种算力分配方式在x86服务器上很常见放到ARM单板上也同样成立。2. 开始前的环境准备2.1 硬件选型与避坑不是所有RK3588开发板都适合跑虚拟机我第一个建议就是选板子时把内存和存储放在第一位。RK3588硬件上支持最大32GB内存但市面上很多入门板子只焊了4GB或8GB。跑虚拟机时宿主机本身需要内存Guest也需要独占一部分内存4GB内存颗粒感太强跑个2GB的虚拟机都很局促。有条件直接上16GB或32GB版本体验完全是两个层次。存储方面千万不要用TF卡来承载虚拟机磁盘。TF卡随机读写能力太弱跑起数据库或者编译任务会卡得你怀疑人生。优先选择带M.2 NVMe接口的板子把系统镜像和虚拟机磁盘都放在NVMe SSD上其次是SATA接口的SSD实在没有扩展接口再考虑高性能TF卡但要有性能打折的心理准备。虚拟机场景下磁盘IOPS就是生产力这个钱不能省。散热同样不能忽视。RK3588的大核满载时的发热不小如果散热片太小或者没有主动风扇CPU温度一上来就会触发降频虚拟机性能会跟着大幅缩水。我手头这块板子装了一个带温控风扇的散热器编译大项目时温度控制在65度以内性能稳定很多。另外建议准备好稳压电源很多开发板的供电接口看着一样但标称电流不同供电不足会导致NVMe掉盘甚至系统重启这是非常隐蔽的坑。2.2 宿主机系统与固件宿主机系统我建议直接用Ubuntu 24.04 LTS的ARM64版本。原因很简单RK3588的社区支持在Ubuntu上最活跃内核版本较新qemu和libvirt等虚拟化软件包在软件源里就是现成的基本不需要自己从源码编译。拿到板子后首先做两件事。第一去板卡厂商官网或者社区下载最新固件更新SPL和U-Boot。虚拟机依赖的很多硬件特性需要固件配合旧固件可能会存在PCIe不稳定、内存初始化不完整的问题。第二写完后开机立刻执行系统更新sudo apt update sudo apt full-upgrade sudo reboot更新完内核和固件后再确认一下当前内核版本。如果内核里连KVM模块都没有后面所有步骤都会卡住。2.3 第一步检查/dev/kvm和内核模块在开始创建虚拟机之前必须先确认宿主机内核的KVM支持是否正常工作。ARM64平台上检查起来比x86简单不需要看CPU的vmx/svm标志直接看设备节点和内核日志就行。ls -l /dev/kvm如果能看到/dev/kvm这个字符设备说明KVM模块已经加载这是最直接的健康信号。接着再看一下模块状态和系统日志lsmod | grep kvm dmesg | grep -i kvm sudo virt-host-validatevirt-host-validate是libvirt自带的检测工具会检查KVM可用性、设备节点权限、内核配置等一堆项目。如果它提示某一步失败不要急着建虚拟机先把这一步排掉。最常见的失败原因是板卡厂商自带的精简内核没编KVM模块解决办法是换成官方主线内核或者Ubuntu提供的内核包重新启动后就正常了。如果一切正常还可以顺手把当前用户加进libvirt和kvm组避免后面每次操作都加sudosudo usermod -aG libvirt,kvm $USER newgrp libvirt3. KVM虚拟机搭建实操3.1 安装虚拟化工具链Ubuntu下安装KVM相关的软件包比较傻瓜化一条命令搞定sudo apt install qemu-system-arm libvirt-daemon-system libvirt-clients virtinst virt-manager cloud-image-utils其中qemu-system-arm这个包会提供qemu-system-aarch64二进制文件它就是KVM模式下的虚拟化后端。在老文档里你可能会看到qemu-kvm这个包名那是x86平台上的习惯叫法在ARM64的Ubuntu上直接安装qemu-system-arm即可。libvirt-daemon-system负责启动和常驻libvirtd服务virtinst提供virt-install命令行工具virt-manager是图形化管理界面cloud-image-utils里带cloud-localds用来生成cloud-init的seed镜像。装完之后确认服务在运行sudo systemctl enable --now libvirtd sudo systemctl status libvirtd3.2 准备系统镜像qcow2与cloud-init创建虚拟机之前先决定磁盘格式。qcow2是目前最通用的选择支持写时复制、快照、压缩缺点是有一点元数据开销raw格式性能最好但占满全部空间而且没有快照能力。在开发板上折腾虚拟机我推荐用qcow2方便随时做快照回滚。然后下载Ubuntu官方Cloud镜像。这个镜像是专门为云环境优化的qcow2镜像自带cloud-init开机后会自动做网络配置、账号配置和SSH密钥注入特别适合无头服务器场景。wget -P /var/lib/libvirt/images https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img接着创建一个叠加盘多个虚拟机可以基于同一个基础镜像派生基础镜像始终保持只读sudo qemu-img create -b /var/lib/libvirt/images/ubuntu-24.04-server-cloudimg-arm64.img -F qcow2 -f qcow2 /var/lib/libvirt/images/srv01.qcow2 30G再准备cloud-init的配置文件告诉系统创建什么用户、注入哪些SSH公钥cat /tmp/user-data EOF #cloud-config users: - name: ubuntu sudo: ALL(ALL) NOPASSWD:ALL groups: sudo shell: /bin/bash ssh_authorized_keys: - ssh-rsa 你的SSH公钥粘贴在这里 - name: root ssh_authorized_keys: - ssh-rsa 你的SSH公钥粘贴在这里 package_update: true packages: - qemu-guest-agent EOF cat /tmp/meta-data EOF instance-id: srv01 local-hostname: srv01 EOF sudo cloud-localds /var/lib/libvirt/images/srv01-seed.iso /tmp/user-data /tmp/meta-data如果你更习惯用安装盘交互式装系统也可以跳过Cloud镜像直接下载Ubuntu Server的ARM64 ISO通过VNC图形界面安装原理一样本文后面全部以Cloud镜像路径为例。3.3 virt-install一行命令创建第一台虚拟机所有材料准备好之后创建虚拟机就变成了一条命令的事sudo virt-install \ --name srv01 \ --memory 4096 \ --vcpus 4,sockets1,cores4,threads1 \ --cpu host-passthrough \ --disk path/var/lib/libvirt/images/srv01.qcow2,formatqcow2,busvirtio,cachewriteback \ --disk path/var/lib/libvirt/images/srv01-seed.iso,devicecdrom \ --network networkdefault,modelvirtio \ --os-variant ubuntu24.04 \ --import \ --graphics none \ --console pty,target_typeserial \ --noautoconsole这里每个参数都值得多说两句。--cpu host-passthrough是关键中的关键它让虚拟机直接看到宿主机CPU的完整特性集。ARM平台上如果不用这个参数QEMU可能默认暴露一个保守的CPU型号某些需要特定ISA特性的程序会在虚拟机里报非法指令错误。--vcpus 4,sockets1,cores4,threads1指定了vCPU拓扑。在RK3588上我建议Guest核心数和物理大核数对齐这样后续做CPU绑定时最顺手。--disk里的busvirtio指定硬盘走半虚拟化总线cachewriteback是为了性能后面性能优化部分我会专门解释cache模式的区别。--import告诉virt-install直接启动现有镜像而不是从安装介质引导。cloud-init的seed盘通过devicecdrom挂载进去虚拟机第一次启动时cloud-init会自动读取并完成初始化过程不需要人工干预。运行完毕后确认虚拟机状态sudo virsh list --all sudo virsh domifaddr srv01等一两分钟让cloud-init跑完domifaddr能看到Guest的IP地址直接SSH进去就行。如果默认NAT网络没启动需要先执行sudo virsh net-start default。3.4 网络打通NAT还是桥接libvirt默认创建了一个NAT网络对应虚拟网桥virbr0虚拟机通过它共享宿主机IP上网。NAT的好处是不用改宿主机网络也不影响局域网开箱即用缺点是从外部网络无法直接访问虚拟机端口要额外做转发。如果虚拟机要给局域网里其他设备提供服务桥接模式更合适。桥接就是让虚拟机直接挂在物理局域网里拥有独立IP外部访问零障碍。配置桥接前先确保宿主机有两个网口或者你有远程访问板子的备用通道因为一旦把唯一网络接口改成网桥配置出错就会直接断网。在Ubuntu 24.04里用Netplan创建网桥比较方便。修改/etc/netplan/01-netcfg.yaml文件名以实际为准network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no bridges: br0: interfaces: [eth0] dhcp4: yes parameters: stp: false forward-delay: 0应用配置后再把libvirt的默认网络改成桥接模式或者直接用virsh定义一个新的网桥网络cat /tmp/host-bridge.xml EOF network namehost-bridge/name forward modebridge/ bridge namebr0/ /network EOF sudo virsh net-define /tmp/host-bridge.xml sudo virsh net-start host-bridge sudo virsh net-autostart host-bridge之后创建虚拟机时网络参数改成--network bridgebr0,modelvirtio虚拟机就会直接出现在局域网里能拿到路由器下发的局域网IP。4. 性能优化让虚拟机跑出接近原生的速度4.1 CPU优化把vCPU钉在A76大核上这是整个性能优化里收益最大的一步但也是很多人忽略的一步。RK3588是大小核异构架构在Linux下CPU编号一般是CPU0到CPU3是A55小核频率最高1.8GHzCPU4到CPU7是A76大核频率最高2.4GHz。如果QEMU进程被调度器扔到小核上虚拟机的计算性能会掉一大截而且你自己很难第一时间察觉。先确认CPU编号和拓扑结构lscpu | egrep CPU\(s\)|Socket|Core|Model name然后进入安全模式把虚拟机的4个vCPU分别绑定到4个大核上。假设虚拟机name是srv01物理大核为4-7for i in 0 1 2 3; do sudo virsh vcpupin srv01 $i $((i4)) donevirsh vcpupin指定的是vCPU与物理CPU的映射关系。执行完再查看一下是否生效sudo virsh vcpupin srv01如果需要更彻底的隔离可以在宿主机内核参数里把大核从通用调度池中拿走让宿主机自己的进程只跑小核。修改/etc/default/grubGRUB_CMDLINE_LINUXisolcpus4,5,6,7 nohz_full4,5,6,7 rcu_nocbs4,5,6,7执行sudo update-grub并重启之后Guest独占大核宿主机应用干扰就很小了。如果你的板子不使用GRUB而是extlinux引导对应修改/boot/extlinux/extlinux.conf里APPEND参数即可。CPU频率调度器也改成performance模式避免系统频繁变频导致延迟抖动sudo apt install linux-tools-common cpufrequtils sudo cpupower frequency-set -g performance大核小核混跑是RK3588上虚拟机性能上不去的头号原因只要这一步做对了后面很多优化才谈得上。4.2 内存优化大页与关闭balloonQEMU默认对内存做了很多动态管理比如balloon驱动会允许宿主机在虚拟机运行时回收和重新分配内存。但在虚拟化服务器场景里这种动态调整反而有害它会造成Guest内内存地址映射频繁变化增加TLB压力关键业务响应时延不稳定。我的建议是直接禁用balloon让虚拟机内存保持固定sudo virsh edit srv01在XML配置里添加memballoon modelnone/接下来是HugePages也就是大页内存。默认4KB页面会让QEMU的地址转换产生大量TLB miss换成2MB甚至1GB的大页能显著降低虚拟化开销。在RK3588上预留2MB大页是比较稳妥的做法兼顾兼容性和收益。修改GRUB配置GRUB_CMDLINE_LINUXhugepagesz2M hugepages4096这里预留了4096个2MB页一共8GB内存。如果板子内存是16GB可以留这么多如果只有8GB建议改成hugepages2048也就是4GB给宿主机留够余量。更新引导并重启sudo update-grub sudo reboot重启后检查大页是否生效cat /proc/meminfo | grep -E HugePages_Total|Hugepagesize然后编辑虚拟机XML加入内存backing配置sudo virsh edit srv01memoryBacking hugepages/ /memoryBacking配置完成后恢复虚拟机或者直接重启虚拟机让它使用大页内存运行。实测在内存密集型负载下开启大页后虚拟机的性能和稳定性都会有可感知的提升。4.3 存储优化virtio-blk与iothread取舍存储是虚拟机体验的第二个大瓶颈。首先明确一点Guest磁盘总线必须用virtio绝对不要模拟IDE或者SATA控制器。virtio-blk是半虚拟化设备Guest通过共享内存环形队列直接和宿主机交换数据绕开了硬件设备模拟的开销。如果你手里是NVMe SSD还能更进一步。QEMU默认由一个主线程处理所有磁盘请求在高IO并发场景下这个线程会成为瓶颈。解决办法是启用iothread把磁盘IO处理分散到独立线程。编辑虚拟机XMLiothreads2/iothreads然后给磁盘设备加上iothread编号disk typefile devicedisk driver nameqemu typeqcow2 cachewriteback iothread1/ source file/var/lib/libvirt/images/srv01.qcow2/ target devvda busvirtio/ /disk这里可以针对不同负载调整cache模式。writeback表示Guest写操作先进宿主page cacheQEMU认为写完成性能最好但断电时数据可能丢失none表示绕过宿主缓存用O_DIRECT直接写磁盘数据一致性更好性能略低writethrough最安全但最慢。我个人在开发板虚拟机上的配置习惯是普通编译、测试环境用cachewriteback加iothread追求体验跑数据库或者重要业务用cachenone宁可少一点性能也要减少掉电丢数据的风险。另外qcow2叠加盘首次分配块时会有额外开销如果磁盘空间充裕创建时用-o preallocationfull预分配可以避免运行中出现卡顿。4.4 网络优化virtio多队列网络方面同样遵循一个原则网卡模型用virtio别用e1000之类的模拟设备。virtio-net配合vhost-net内核模块Guest网络包可以直接在宿主内核里处理性能已经很接近物理网卡。默认virtio-net是单队列的高并发流量会集中在一个CPU上处理中断和收发都挤在一起。多队列特性可以把流量分散到多个vCPU上并行处理。编辑虚拟机XML给网络接口加queues参数interface typebridge source bridgebr0/ model typevirtio/ driver namevhost queues4/ /interface然后进入虚拟机内部把网卡队列数量开到4个sudo ethtool -L eth0 combined 4多队列只有在Guest vCPU数量大于等于队列数时才有意义这也印证了前面为什么建议虚拟机的vCPU数和大核数对齐。确认宿主机vhost_net模块已加载lsmod | grep vhost如果宿主机的网络中断总是打在一个固定的CPU上可以把网卡中断的smp_affinity手动设到小核上把大核从宿主机的网络处理中释放出来进一步减少对虚拟机CPU的干扰。整个过程配合CPU绑定的效果在iperf3测试里网络吞吐差异非常明显。4.5 一个快速压测与验证清单配置做完之后别急着跑业务先在虚拟机和宿主机两侧做一轮快速压测确认优化真正生效。CPU用sysbench验证sudo apt install sysbench sysbench cpu --threads4 --cpu-max-prime20000 run内存带宽用sysbench的memory测试sysbench memory --memory-block-size1M --memory-total-size10G run磁盘用fio测试随机写sudo apt install fio fio --namerandwrite --rwrandwrite --bs4k --size2G --iodepth32 --numjobs4 --runtime30s --group_reporting网络用iperf3测宿主机到虚拟机的吞吐# 虚拟机里先启动服务端 iperf3 -s # 宿主机里跑客户端 iperf3 -c guest-ip -P 4以我手头这块RK3588开发板为例32GB内存版本加NVMe SSD4 vCPU绑定大核后sysbench CPU跑分比未绑定前提升了30%以上iperf3局域网内到虚拟机大约能跑满双千兆聚合的水平如果板子带2.5G网口还能更高NVMe加iothread配合writeback虚拟机内随机写IOPS比默认配置翻了不止一倍。不同板子和不同固件数据会有差异但优化方向带来的收益是明确的。5. 常见问题与排查技巧5.1 报错与排查对照表这一路折腾下来我把最常遇到的几个问题整理成了一张对照表按照这个顺序排查能省不少时间。现象可能原因解决办法/dev/kvm不存在内核没编KVM模块或固件太旧换官方主线内核更新U-Boot/SPL固件执行modprobe kvmvirt-host-validate不通过KVM权限问题或模块未加载把用户加入libvirt/kvm组重启libvirtd虚拟机启动后无IPcloud-init没跑完或NAT网络未启动virsh net-start defaultvirsh net-autostart default串口console卡住cloud-init正在初始化或安装介质不对等2至3分钟SSH尝试连接查看Guest串口日志虚拟机性能还不如宿主机vCPU落到了小核上用vcpupin绑定大核加isolcpus隔离性能模式调frequency网络吞吐上不去单队列网卡中断集中在单核开启virtio多队列Guest内ethtool -L扩大队列磁盘写入掉速明显cache模式不当或没有iothread换NVMe盘cachewriteback或none加iothreadHugePages预留后无法启动预留内存超过了物理内存调低hugepages数量确认GRUB重启过libvirt报权限错误当前用户不在libvirt组sudo usermod -aG libvirt,kvm $USER后重新登录5.2 关于隔离与断电保护的补充虚拟机跑起来容易要稳定跑几个月才是真功夫。开发者板不像服务器有专业电源管理和硬件监控以下几个细节需要特别注意。第一掉电保护。如果你给虚拟机开了cachewriteback断电瞬间Guest内存里可能有数据还没落盘重启后qcow2有损坏风险。备一个支持自动关机的UPS成本最低或者重要数据目录单独挂载网络盘。第二日志落盘。建议给虚拟机的序列号console开着虚拟机崩溃时至少能通过串口日志定位问题。第三定期快照。qcow2的优势就是快照方便重大变更之前执行一次virsh snapshot-create-as srv01 backup01出问题可以秒级回滚。5.3 后续扩展方向虚拟机稳定运行之后还能继续往下玩的东西不少。第一利用cloud-init做虚拟机模板化把常用软件和配置写进镜像新建一台虚拟机只需几十秒。第二用libvirt的hook脚本或者systemd服务做虚拟机开机自启和故障自动拉起实现简单的高可用。第三研究RK3588上的IOMMU和设备直通尝试把USB控制器或者GPU直通给虚拟机这需要板卡固件配合不是每块板子都能成功但值得折腾。把这些基础项调好RK3588这块开发板完全可以充当一台低功耗的ARM虚拟化服务器同时跑编译任务、测试环境、服务进程都游刃有余。我在实际使用中最大的体会是ARM单板上的KVM瓶颈从来不在虚拟化本身而在“细节”二字。有人觉得开发板跑KVM是噱头但其实把CPU绑定、大页内存、virtio设备这几件事做扎实它一样能稳定扛住生产负载。接下来想在这个方向上继续深入的朋友建议从设备直通和内核性能剖析入手那两个领域还有大量值得探索的空间。