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

资讯详情

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

RK3588上的KVM虚拟化:从环境搭建到性能调优

RK3588上的KVM虚拟化:从环境搭建到性能调优 一块手掌大的开发板上同时跑着四五个完整的Linux服务器每个都能独立重启、配置网络、跑数据库、对外提供SSH服务互相之间互不干扰——这不是x86机房里的场景而是我最近在RK3588 ARM平台上用KVM虚拟化捣鼓出来的效果。ARM架构 KVM这两个词放在一起曾经让人觉得是“能跑但别指望好用”的组合但实际折腾下来RK3588这颗芯配上KVM完全能当一台正经的虚拟化宿主机来用。这篇文章把我从零开始踩坑、调优、稳定运行的全过程整理出来包含环境搭建、虚拟机创建、CPU/内存/IO性能调优和常见问题排查。适合手里有RK3588开发板RK3588芯片的各类开发板均可、想在ARM平台上跑虚拟化玩意的朋友也适合需要在嵌入式设备上做多系统隔离的开发者参考。我会把每一步为什么这么做、参数怎么选、踩了哪些坑都写清楚尽量做到照着操作就能复现。1. 为什么要在RK3588上折腾KVM1.1 一块芯片里的多台“服务器”RK3588是瑞芯微的旗舰级SoCCPU部分由4个Cortex-A76大核和4个Cortex-A55小核组成典型配置下能跑到2.4GHz左右GPU、NPU、8K视频编解码单元一应俱全内存支持到LPDDR4X / LPDDR5最大容量可以到32GB。这种规格放在几年前就是一台中端台式机的水准多核性能和内存容量完全撑得起多个虚拟机的开销。但光是“性能够”还不够。KVM虚拟化要真正跑得好必须依赖CPU硬件虚拟化扩展和中断控制器的虚拟化支持。传统的ARM 32位时代大部分SoC不支持硬件虚拟化只能靠QEMU做纯软件模拟TCG模式性能损耗大得离谱跑个Linux启动过程都要好几分钟。RK3588采用了Armv8.2-A架构自带了完整的虚拟化扩展Virtualization Extensions包括EL2异常级别、嵌套虚拟化支持、GIC中断虚拟化等。有了这些硬件基础KVM才能在ARM平台上接近原生地运行虚拟机。我实际用下来在RK3588上创建的虚拟机跑系统负载CPU开销比纯QEMU模拟少了一个数量级虚拟机内跑编译任务和宿主机直接编译的速度差几乎可以忽略这种体验在几年前是难以想象的。1.2 KVM、容器和纯模拟方案怎么选在ARM平台上做“多系统”方案我见过不少人走了弯路。简单把三个主流方案梳理一下你就明白为什么最终需要KVM。容器Docker/LXC是最轻量的隔离方案但它共享宿主机内核。如果你只是跑Web服务、数据库这种应用容器足够但如果客户机需要不同内核版本、需要修改内核模块、需要自己定制系统镜像容器就会卡死。我做嵌入式系统验证经常要测试不同发行版、不同内核配置的启动兼容性容器完全满足不了只能上真正的虚拟化。QEMU纯软件模拟TCG模式不用任何硬件辅助兼容性最好什么架构都能模拟但性能瓶颈极大。TCG模式下每条指令都要经过翻译执行CPU开销是KVM的数倍到数十倍。在RK3588上跑一个arm64的虚拟机TCG模式启动都要几十秒运行系统负载更是卡到怀疑人生。这只能用来做临时测试完全不适合当日常方案。KVM走的是硬件辅助虚拟化路线客户机的CPU指令大部分直接运行在物理CPU上虚拟化开销主要在内存地址转换Stage-2 MMU和中断虚拟化上。在RK3588上的实测表现CPU密集型任务的性能损耗通常在5%到10%这个范围内完全在可用区间。所以这个项目的最终选择就是宿主机跑标准Linux系统通过KVM创建多个ARM64虚拟机各自独立运行互不干扰。2. 硬件选型与系统基础准备2.1 板卡选择与系统版本在RK3588上做KVM板卡选择比较关键。市面上基于RK3588芯片的开发板不少Orange Pi 5系列、Radxa Rock 5系列、瑞芯微官方EVB以及各种RK3588核心板理论上KVM功能都支持但不同板卡的U-Boot固件、内核版本、设备树配置差异很大直接影响虚拟化能力是否正常启用。我这里有两点经验第一选Soc厂商官方或生态成熟度高的板子U-Boot和内核更新及时踩坑后能搜到解决方案的概率大得多第二内存容量不要少于8GBKVM虽然能跑但要同时跑多个带2GB内存的虚拟机加上宿主机自身的开销8GB是起步16GB才舒服。系统镜像方面我用的是Ubuntu 24.04的rootfs内核版本6.1以上。这里有个细节RK3588的官方BSP内核有时落后于主线内核而KVM相关功能比如GIC虚拟化支持、vCPU调度优化在主线内核上更加完善。有条件的话优先使用接近主线的新内核可以省去很多后续调优的麻烦。我自己在迁移到6.6内核后虚拟机的网络中断处理性能明显提升。2.2 确认硬件虚拟化能力是否就绪系统跑起来后第一件事不是急着装KVM而是确认硬件虚拟化能力有没有被正确暴露。ARM平台和x86不一样没有vmx或svm这种直接可见的CPU标志位但我们可以通过几个途径来确认。先看/proc/cpuinfo确认CPU feature列表中有没有evtstrm、asimddp这类特征字段同时用lscpu查看CPU支持的扩展重点关注Armv8-A虚拟化相关的部分。更直接的方法是查看内核启动日志和KVM模块是否加载成功dmesg | grep -i kvm dmesg | grep -i virtualization ls -l /dev/kvm如果KVM已经正常工作/dev/kvm文件会存在dmesg里会出现类似“kvm [1]: Hyp mode initialized successfully”的日志。我遇到过一种情况内核已经装好了但设备树里没配置GIC的虚拟化控制相关节点导致/dev/kvm一直没有出现。这时候需要检查设备树配置和U-Boot启动参数有些固件需要显式传递kvm-arm相关的参数才能打开虚拟化扩展。还有一种比较隐蔽的坑部分RK3588板卡的U-Boot默认禁止了EL2异常级别的进入权限需要更新固件或者改启动参数。出现这种情况时KVM模块加载会失败日志里会提示“CPU does not support virtualization”之类的话排查方向要往bootloader上找而不是怀疑内核。3. KVM环境搭建从工具链到第一台虚拟机3.1 安装QEMU、libvirt与guest工具确认KVM就绪后开始安装虚拟化管理工具链。在Ubuntu上核心安装包包括QEMU系统模拟器、libvirt守护进程、虚拟机管理工具virtinst和可选的管理界面virt-manager。sudo apt update sudo apt install -y qemu-system-arm qemu-utils \ libvirt-daemon-system libvirt-clients virtinst \ virt-manager cloud-image-utils这里要解释一下ARM64平台上虽然包名叫qemu-system-arm但安装后提供的是qemu-system-aarch64命令用于模拟ARM 64位系统。另外libvirt-daemon-system会创建libvirt-qemu用户和libvirtd服务这是管理虚拟机的核心服务。安装完成后把当前用户加入libvirt和kvm用户组否则普通用户无法管理虚拟机sudo usermod -aG libvirt,kvm $USER sudo systemctl enable --now libvirtd记得重新登录或者执行newgrp libvirt让用户组生效。我建议顺手把/etc/libvirt/libvirtd.conf里的unix_sock_group和unix_sock_rw_perms看一眼确认默认配置允许libvirt组访问管理套接字这样后续用virsh list --all时才不会被权限问题卡住。3.2 准备虚拟机镜像与发布源创建虚拟机之前需要准备用户客户机的根文件系统镜像。这一步有几种做法我推荐用官方云镜像省去手工安装系统和配置引导的麻烦。Ubuntu官方提供ARM64的cloud镜像ubuntu-24.04-server-cloudimg-arm64.img这个镜像是qcow2格式内置cloud-init启动后可自动配置用户和网络。先用qemu-img把镜像复制一份并扩容sudo mkdir -p /var/lib/libvirt/images cd /var/lib/libvirt/images sudo wget https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img sudo cp ubuntu-24.04-server-cloudimg-arm64.img rk3588-vm1.qcow2 sudo qemu-img resize rk3588-vm1.qcow2 20G也可以直接用virt-install从ISO镜像安装系统但云镜像方式更快更可复现而且方便后续批量创建多个虚拟机。如果你手里有已经制作好的rootfs比如自己裁剪的嵌入式文件系统也可以直接打包成qcow2镜像用--import导入。3.3 用virt-install创建第一台虚拟机环境准备好后用virt-install创建虚拟机。这是整个流程中最核心的一步参数选择直接决定虚拟机的性能和稳定性。sudo virt-install \ --name vm1 \ --memory 4096 \ --vcpus 4 \ --cpu host-passthrough \ --arch aarch64 \ --import \ --disk path/var/lib/libvirt/images/rk3588-vm1.qcow2,formatqcow2,busvirtio \ --network networkdefault,modelvirtio \ --os-variant ubuntu24.04 \ --graphics none \ --console pty,target_typeserial \ --boot hd逐个解释关键参数的含义--cpu host-passthrough让虚拟机直接透传宿主机的CPU特性而不是使用QEMU默认的兼容CPU模型。ARM平台上这个参数对性能影响很大我测试过不开启时虚拟机内的CPU特性受限某些依赖新指令集的程序会跑不起来。--disk ... busvirtio磁盘使用virtio半虚拟化驱动。virtio是KVM性能的基础比默认的ide或sd模拟方式高数倍IO性能。--network networkdefault,modelvirtiolibvirt默认NAT网络客户机通过virtio-net访问外网。--graphics none --console pty,target_typeserial无图形界面通过串口控制台访问客户机这符合开发板场景的无头操作习惯。创建后启动虚拟机用串口控制台进入系统sudo virsh start vm1 sudo virsh console vm1cloud-init会在首次启动时自动完成系统初始化。默认登录用户一般是ubuntu初始密码在镜像发布说明里能查到也可以提前用cloud-localds命令做一个包含密码或SSH公钥的seed镜像挂载上去。3.4 网络模式选型NAT还是桥接virt-install默认创建的虚拟机网络是NAT模式宿主机转发客户机流量到外网。这种模式配置简单但客户机之间的网络隔离较差外部设备也无法主动访问虚拟机内的服务对于需要对外提供服务的场景比如在RK3588上同时跑Web服务器和数据库服务就不太合适了。如果需要让虚拟机像独立设备一样接入局域网桥接网络是更好的选择。创建桥接设备并让物理网卡加入桥接sudo nmcli connection add type bridge ifname br0 con-name br0 sudo nmcli connection modify br0 ipv4.addresses 192.168.1.100/24 ipv4.gateway 192.168.1.1 ipv4.dns 192.168.1.1 ipv4.method manual sudo nmcli connection add type ethernet slave-type bridge con-name eth0-br0 ifname eth0 master br0 sudo nmcli connection up br0然后在创建虚拟机时把--network networkdefault改成--network bridgebr0,modelvirtio即可。桥接模式下客户机的网络性能更接近物理机延迟也更低我实际跑下来比NAT模式高大约15%到20%的吞吐。4. 性能调优实战把KVM的潜力榨出来4.1 CPU优化vCPU绑定与调度策略虚拟机创建简单但要让性能接近物理机CPU这块必须仔细调。ARM平台和x86类似vCPU是宿主机上的线程如果放任系统调度器自由分配这些线程cache局部性差、跨核调度频繁虚拟机的性能会明显波动。我把vCPU固定绑定到大核上首先查看CPU拓扑和线程映射lscpu -eRK3588的CPU编号在两个cluster之间交替排列大核对应的CPU编号需要从lscpu输出确认。然后通过virsh vcpupin把每个vCPU绑定到指定物理CPUvirsh vcpupin vm1 0 4 virsh vcpupin vm1 1 5 virsh vcpupin vm1 2 6 virsh vcpupin vm1 3 7如果没有特殊需求我一般把大核留给虚拟机和关键中断处理小核跑系统服务。这里的关键是不要让多个vCPU争抢同一个物理CPU否则虚拟化开销会直线上升。绑定好后可以用virsh vcpuinfo vm1查看绑定结果。接着设置CPU调频策略。RK3588默认的调频策略是schedutil或ondemand负载上来时频率切换有延迟虚拟机会感觉到卡顿。把CPU调频模式固定到performance能稳定提升响应sudo cpupower frequency-set -g performance如果有多个CPU核心都切换到performance模式注意同时执行。这个设置在重启后失效需要写成systemd服务或放入rc.local。4.2 内存优化大页内存与balloon取舍内存虚拟化最大的开销来自TLB转换后备缓冲Miss后的stage-2地址翻译启用HugePages能显著减少TLB Miss提高大内存虚拟机的内存访问性能。在宿主机上预留大页内存池。RK3588支持2MB和1GB两种大页大小我建议用2MB灵活性更好。预留方式echo 4096 /proc/sys/vm/nr_hugepages mkdir -p /dev/hugepages mount -t hugetlbfs hugetlbfs /dev/hugepages为了让配置重启后依然生效修改/etc/sysctl.d/99-hugepages.confvm.nr_hugepages4096然后在libvirt的虚拟机XML中添加内存配置。用virsh edit vm1修改memoryBacking和memory区域memory unitKiB4194304/memory currentMemory unitKiB4194304/currentMemory memoryBacking hugepages/ /memoryBacking修改后重启虚拟机生效。可以在客户机内检查大页是否生效grep -i hugepages /proc/meminfo另外要注意libvirt默认会启用内存balloon设备允许动态调整虚拟机内存但这在ARM平台上有时会引发客户机内存识别异常。性能优先的虚拟机建议把balloon禁用在XML中去掉memballoon相关配置即可。内存分配到位后虚拟机内的大内存应用比如编译大型项目能感受到明显改善。4.3 存储优化virtio与缓存策略存储方面virtio-blk是必须的。创建虚拟机时如果忘了指定busvirtio后面可以用virsh edit把磁盘总线改成virtio并重启。但光有virtio还不够缓存策略对性能影响也很大。我通常把磁盘缓存策略设为none配合iothreads这样数据直接通过页面缓存交换避免QEMU进程空间内的额外拷贝。修改XML中的driver行driver nameqemu typeqcow2 cachenone iothreads/如果用裸设备比如直接物理分区或整块NVMe盘做虚拟机磁盘cachenone是最佳选择如果是qcow2镜像还需要考虑discard和detect_zeroes参数可以在driver里加上discardunmap和detect_zeroesunmap让虚拟机内的fstrim指令真正释放宿主机的镜像空间。多队列支持是另一个容易被忽略的性能点。现代内核的virtio-blk驱动支持多队列每个IO队列绑定一个vCPU可以提升并发IO吞吐。在libvirt XML中给磁盘加上driver ... queues4客户机内确认/sys/block/vda/device/queue目录下出现多个队列即可。4.4 网络优化开启vhost-net与多队列网络IO是虚拟机最容易出现瓶颈的地方。默认的virtio-net虽然比纯模拟好很多但每个包都要经过QEMU进程上下文切换开销不小。启用vhost-net后报文处理的一部分会移动到内核态内核模块中完成减少用户态和内核态的切换网络吞吐和延迟都能改善。在libvirt中启用vhost-net编辑虚拟机XML的interface部分interface typenetwork source networkdefault/ model typevirtio/ driver namevhost queues4/ /interfacequeues4对应客户机CPU数量开启后客户机网卡会创建多个virtqueue配合客户机的多核中断处理能力多连接传输性能提升明显。用ethtool -L ens3 combined 4客户机内网卡名可能不同设置多队列中断然后用ethtool -S ens3查看接收和发送队列统计确认多个队列都在处理包。需要留意的是vhost-net依赖内核模块vhost_vsock实际应为vhost_net下称vhost子模块的正常加载。如果宿主机内核没有编译该模块driver namevhost配置会导致虚拟机网络无法启动此时需要安装或编译内核模块。在Ubuntu上可以用modprobe vhost_net手动加载并把模块加入/etc/modules-load.d/避免重启后丢失。4.5 实测数据参考调优完成后我在宿主机和虚拟机内分别跑了几个通用测试这里给一组供参考的数据RK35888G内存宿主Ubuntu 24.04 kernel 6.64 vCPU测试项宿主机原生KVM虚拟化未调优KVM虚拟化调优后多线程编译Linux内核模块耗时100%基准116%105%网络TCP吞吐本机iperf3940 Mb/s720 Mb/s930 Mb/s磁盘顺序读fioqd32使用NVMe实测值约为原生75%约为原生92%内存读写带宽mbw100%基准87%96%调优的核心就是把宿主机的硬件资源尽可能直接暴露给客户机减少QEMU的翻译层和调度层参与。ARM平台上虽然虚拟化扩展不如x86那么成熟但明确按路线图做下来效果差距还是很大的。5. 常见问题与排查技巧实录5.1 /dev/kvm不存在或权限不足这是最容易遇到的第一个拦路虎。如果你执行ls -l /dev/kvm报没有这个文件先检查内核模块modprobe kvm modprobe kvm_arm # 有些内核模块命名是kvm-arm如果模块加载失败dmesg会给出具体原因多数情况是CPU虚拟化扩展没有被bootloader开启。我在一块RK3588工控板上遇到过U-Boot环境变量里禁用了EL2支持的情况在U-Boot命令行里检查cpu virtualization相关设置具体变量名因固件而异或者在板卡供应商的文档里找启用虚拟化的开关。这部分不同板卡差异很大网上搜一下自己型号就能找到方案。如果你能看到/dev/kvm但普通用户无法访问执行sudo chown root:kvm /dev/kvm sudo chmod 0660 /dev/kvm不过这种设置重启后会失效更好的方式是确认/etc/udev/rules.d/里有没有kvm设备的udev规则。Ubuntu默认有60-kvm.rules会创建kvm用户组并设置权限没有的话手动补一条。5.2 虚拟机启动报错“KVM not supported”或“failed to initialize KVM”这个报错会出现在virsh start时或者在QEMU命令行中看到kvm: failed to initialize KVM: Function not implemented。排查思路分两层。第一层确认KVM模块是否加载成功。执行kvm-ok安装cpu-checker包后可用如果输出包含“KVM acceleration can be used”之类的内容说明硬件条件满足。如果提示“Your CPU does not support KVM”基本可以断定是固件或内核配置问题按照5.1的方式检查。第二层是确认QEMU是否以KVM加速模式运行。在libvirt中需要保证虚拟机XML里cpu modehost-passthrough和features配置没问题特别是有没有误设cpu modecustom且指定了不支持虚拟化的CPU model。我用virtinst --cpu host-passthrough创建时就没有这个问题但手动创建或修改XML很容易踩这个坑。5.3 大页内存分配失败配置了hugepages后虚拟机启动报“unable to map backing store for guest RAM”或“Cannot allocate memory”错误一般是宿主机大页内存不足。先看预留是否生效cat /proc/meminfo | grep Huge如果HugePages_Total为0说明sysctl配置没成功检查/etc/sysctl.d/99-hugepages.conf的语法和是否在initramfs阶段就挂载了hugepages。另一个原因是物理内存碎片严重虽然总量充足但连续大页不足。可以试试重启宿主机再预留或者在BIOS/U-Boot里预留内存给大页有些ARM平台需要用mem内核参数让出连续内存区域。我的一个经验是不要把宿主机所有内存都分给虚拟机。大页内存一旦被虚拟机和QEMU占用回收起来很麻烦预留量建议控制在物理内存的一半左右保守一点能避免很多麻烦。5.4 客户机网络吞吐低vhost没有生效虚拟机里iperf3吞吐上不去先检查宿主机的vhost内核模块lsmod | grep vhost_net没有的话加载vhost_net模块并确认虚拟机XML中driver namevhost/正确。如果模块加载成功且配置正确可以在宿主机上用ethtool -S virbr0之类的命令看桥接端口的统计确认数据确实走了vhost路径。还有一种比较隐蔽的问题客户机内的网卡可能没有正确加载多队列驱动。执行ethtool -l ens3查看当前队列数如果只有1个队列用ethtool -L ens3 combined 4上调。部分ARM发行版的网络配置工具如netplan会覆盖ethtool设置需要把ethtool命令写入rc.local或systemd服务确保开机后自动生效。5.5 客户机休眠/暂停功能异常RTOS或系统挂起ARM平台上的ACPI支持目前仍不完善很多RK3588板卡在客户机内核里都没有启用ACPI或休眠功能。如果你在虚拟机里执行suspend或睡眠命令可能会直接挂起因为宿主机的GIC和U-Boot不擅长处理从EL2虚拟化状态下恢复的流程。我踩过这个坑解决方案很粗暴在客户机BIOS如果是UEFI guest或内核启动参数里禁用ACPI的suspend相关功能。比如在客户机GRUB配置里加入acpioff但这会一并失去正常关机管理等功能不太推荐。更好的办法是在客户机里直接用systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target禁用休眠需要重启虚拟机时用virsh reboot或virsh shutdown可靠完成。5.6 嵌套虚拟化支持如果你尝试在KVM客户机里再跑一个KVM比如在虚拟机里创建虚拟化测试环境会遇到和“VMware Workstation 不支持嵌套虚拟化”类似的问题——ARM平台上默认也是不允许的因为客户机内的KVM需要宿主机的虚拟化扩展再次透传进去。ARMv8.3开始的嵌套虚拟化支持还比较有限但通过KVM的kvm-arm.nestedtrue参数可以尝试开启不同内核版本支持度不同。需要宿主机内核、QEMU和客户机内核都支持嵌套虚拟化特性这个配置链比较长。我的建议是尽量避免嵌套KVM如果需要多级虚拟化测试改用QEMU的TCG模式做第二层代价是性能很低但能完成功能性验证。6. 一点额外的实操心得最后分享两个我实际使用中的小技巧。第一个是给虚拟机启停设置主机CPU的自动调频联动。裸机跑重负载时RK3588的频率提升需要一定时间虚拟机里的负载不一定能立刻触发调频。我写了一个简单的脚本在virsh start时强制把CPU governor设为performance关机后恢复为schedutil。这样既不浪费平常待机的功耗又能在虚拟机负载上来时避免卡顿。你可以根据自己的使用习惯改这个逻辑。第二个是针对长时间运行的虚拟机建议定期在宿主机侧观察vmstat和pidstat里QEMU进程的CPU占用。如果某个vCPU线程的CPU占用长期超过90%大概率是客户机内有CPU密集型任务在跑检查客户机进程而不是直接怀疑宿主机。反过来如果所有vCPU线程的CPU都很低但虚拟机内却卡成PPT那问题多半出在IO路径上重点看块设备队列长度和网络中断分布。KVM在ARM平台上的成熟度确实不如x86但随着RK3588这一代高性能ARM SoC的普及很多以前在ARM上做不了的玩法现在都能落地了。我折腾这套东西的初衷只是想在开发板上多跑几个独立的系统环境做测试结果越用越觉得它适合当作一台低功耗家庭服务器或开发实验室里的多租户平台。希望这篇文章能帮你少走几步弯路少熬几个夜。
返回列表