
1. QEMU这东西到底能拿来干什么如果你接触过虚拟化工具肯定绕不开QEMU。它本身是一个开源模拟处理器程序加上KVM之后又变成高性能虚拟机监控器。不夸张地说QEMU是当下最灵活的模拟平台既能模拟x86、ARM64、RISC-V等不同架构的整机也能在Linux上利用KVM走硬件虚拟化让虚拟机性能接近物理机。很多人在x86电脑上跑ARM64系统、做嵌入式开发、搞内核调试或者单纯想开个临时Linux环境用的都是QEMU。这篇文章不会啰嗦太多理论直接结合我自己的实操经验把QEMU常用操作、模拟ARM64的套路、KVM开发调试的技巧以及Windows宿主机和虚拟机之间共享文件夹的配置方法全部拆开讲清楚。不管你是刚接触虚拟化的新手还是被跨架构模拟坑过的老手照着下面内容操作基本都能跑通。我最早接触QEMU是因为要在工作站上模拟ARM64环境。说实话当时被一堆启动参数劝退了光是-M、-cpu、-kernel这些选项就够研究一晚上。后来折腾多了才发现QEMU的设计其实很符合直觉它把整机模拟拆成机器类型、CPU型号、内存大小、存储设备、网络设备等几个模块你只要理解每个模块对应什么命令行其实很好“拼”。之后再用上KVM加速、配置好共享目录这套工具就是开发环境下最可靠的模拟平台之一。下面把它的核心概念先聊透。1.1 两种常见的QEMU工作模式QEMU运行模式可以粗略分成两大类。第一类叫全系统模拟也就是模拟一整套电脑CPU、内存、主板、网卡、磁盘控制器等等里面可以跑完整的操作系统。比如在x86的Linux主机上跑一个ARM64的Ubuntu或者在一个完全没有显示器的服务器上开一个Windows虚拟机都是全系统模拟。第二种叫用户态模拟它只模拟一个CPU体系结构单独跑一个编译给其他架构的Linux程序比如用qemu-aarch64在x86平台上执行ARM64的二进制。这两种模式面向的需求完全不同全系统模拟适合跑整个OS用户态模拟更适合做交叉编译、单体程序测试。日常开发中我们说的“QEMU模拟平台”大部分情况下指全系统模拟。因为这能完整复现目标硬件的运行环境调试内核、跑系统服务、做应用兼容性验证都可靠。用户态模拟虽然启动快、资源占用低但它没法帮你验证系统级的东西只能作为辅助手段。1.2 加速后端TCG和KVM怎么选QEMU本身是用纯软件方式翻译指令的这套翻译器叫TCG。TCG的好处是通用不管在什么架构上都能模拟任何目标架构代价是性能通常只有原生执行的四分之一左右。如果你的主机是Linux而且目标架构和主机架构一致那就可以开启KVM加速。KVM是内核自带的一个虚拟化模块它让QEMU把虚拟机里大部分指令直接在CPU上执行性能和物理机非常接近。选择的原则很简单能开KVM就开KVM不能开再回退到TCG。在x86主机上模拟x86虚拟机直接加上-accel kvm。如果要在x86主机上模拟ARM64因为架构不同只能走TCG性能会慢不少但做开发测试足够了。另外在macOS上有Hypervisor.framework加速Windows上有WHPX这些都属于QEMU的加速后端原理类似。平时开发我主要用Linux KVM跨架构模拟才用TCG。2. 最常用的QEMU启动命令和参数拆解QEMU的启动命令看着长其实逻辑非常清晰。它不像VMware那样有图形界面所有配置都通过命令行参数传递。刚开始会觉得手写命令很痛苦但当你需要在脚本里自动化创建虚拟机时命令行反而是最直观、最方便的。2.1 先拼一条最简启动命令用QEMU跑一个Linux虚拟机最核心的几个参数是机器类型、CPU、内存、内核或磁盘镜像。举个例子在x86主机上启动一个已经装好系统的qcow2镜像命令长这样qemu-system-x86_64 \ -accel kvm \ -cpu host \ -smp 4 \ -m 4096 \ -drive file/path/to/linux.qcow2,ifvirtio,formatqcow2 \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0 \ -nographic逐项解释一下-accel kvm启用KVM加速-cpu host让虚拟机使用宿主机的CPU特性性能最好-smp 4分配4个虚拟CPU-m 4096分配4GB内存-drive指定磁盘镜像-netdev user是用户态网络最简单虚拟机通过NAT共享宿主机网络上网-nographic表示不用图形界面直接把串口输出到终端。如果你只是临时想试一下系统不想装磁盘镜像可以直接用-kernel指定内核配合-initrd挂载initrd。这种方式特别适合调试内核稍后会细讲。2.2 磁盘镜像的创建和扩容QEMU最常用的磁盘格式是qcow2。它支持写时复制、快照、压缩而且一开始只占很少空间等写入数据时才逐渐变大。创建一个默认的qcow2镜像很简单qemu-img create -f qcow2 disk.qcow2 20G这条命令创建了一个最大容量20GB的磁盘但初始文件大小只有几KB。等虚拟机往里写数据文件会自己增长。很多人疑惑“我明明创建了20G为什么宿主上文件才200KB”就是因为qcow2是稀疏分配的。如果磁盘空间不够用了可以用qemu-img resize扩容qemu-img resize disk.qcow2 40G注意扩容只是增加了虚拟磁盘的容量上限分区表还是要进系统里用growpart和resize2fs去扩展分区和文件系统。这一步很多新手会漏掉导致扩容之后进系统一看容量没变化。2.3 网络模式的坑user和bridgeQEMU的-netdev user模式是最省事的它内置了一个用户态NAT网络。虚拟机访问外网没问题外网无法直接访问虚拟机而且虚拟机之间、宿主机与虚拟机之间的通信在某些场景下会受限制。如果只是开个开发环境用user模式就够了。但如果你想在虚拟机里跑一个服务然后让局域网里的其他机器访问就得用桥接网络。在Linux宿主上先创建br0桥接接口再把物理网卡加进去然后QEMU这样启动qemu-system-x86_64 \ -netdev bridge,idnet0,brbr0 \ -device virtio-net-pci,netdevnet0桥接网络的配置比NAT麻烦但它能让虚拟机像一台真正的物理机一样接入局域网。我的经验是日常开发用NAT对外提供服务才用桥接。还有一点要提醒在Windows宿主上用桥接网络需要注意主机防火墙的设置否则虚拟机之间、宿主机之间可能互相ping不通。3. 在x86机器上模拟ARM64qemu-system-aarch64实操模拟ARM64是很多人入手QEMU的直接原因。常见场景包括编译好的ARM64内核启动测试、跑ARM64版Linux发行版、做嵌入式交叉调试。x86上模拟ARM64和同架构模拟完全是两码事性能和启动方式都有不少坑值得单独说说。3.1 跨架构模拟前必须知道的几件事第一跨架构模拟只能用TCG不能开KVM。因为宿主机CPU是x86虚拟机CPU是ARM64指令集不一致没法直接硬件执行。第二启动方式要选对。模拟ARM64整机通常用qemu-system-aarch64机器类型-M很关键常见的有virt、raspi等。virt是QEMU自带的通用虚拟平台外设虚拟化比较完善适合跑标准Linux发行版。第三需要ARM64的内核和设备树。QEMU的virt机器会通过-dtb加载设备树文件或者用UEFI引导程序自动探测硬件。第四性能和磁盘格式注意。TCG模式下CPU越强模拟越快但依然比原生慢不少。我一般在x86服务器上模拟ARM64做编译验证全系统编译内核的时间要比ARM物理机慢3到5倍。磁盘建议用qcow2别用raw镜像因为qcow2按需分配空间模拟大镜像时更灵活。3.2 完整启动ARM64 Linux的步骤首先需要准备一个ARM64的内核镜像和rootfs。以Ubuntu的ARM64云镜像为例下载后可以这样启动qemu-system-aarch64 \ -M virt \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -kernel ubuntu-xx-generic-efi-arm64.file \ -drive fileubuntu-xx.img,ifnone,iddisk0,formatraw \ -device virtio-blk-device,drivedisk0 \ -netdev user,idnet0 \ -device virtio-net-device,netdevnet0 \ -nographic这里有个重要细节-cpu cortex-a72是我在x86宿主机上试过的比较稳定的选择你可以换成neoverse-n1之类的CPU模型具体要看QEMU版本对哪些CPU模型支持得更好。如果启动时找不到设备树可以加上-dtb参数指定dtb文件。很多从x86转过来的同学不习惯看串口输出加-nographic之后所有内核日志和登录界面都会显示在当前终端里调试内核时非常有用。3.3 让模拟的ARM64系统联网跨架构模拟下联网方式几乎只有NAT合适。使用-netdev user,idnet0后虚拟机能通过宿主上网但反向连接会麻烦一些。如果你需要从宿主机直接SSH到模拟的ARM64系统最简单的方法是用hostfwd参数做端口转发qemu-system-aarch64 \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-device,netdevnet0这样宿主机上的2222端口会转发到ARM64虚拟机的22端口然后直接在宿主机执行ssh -p 2222 userlocalhost就能登进系统。我经常用这个方法跑ARM64容器镜像测试不用打开图形界面效率高。此外跨架构网络还有一个常见坑ARM64系统里的网卡名称可能不是eth0而是enp0s1之类。这是因为现代Linux使用可预测网络接口命名。如果你写自动化脚本时习惯写eth0在ARM64虚拟机里会失败。解决办法是启动时加内核参数net.ifnames0或者干脆在脚本里动态获取网卡名。4. QEMU与KVM的组合玩法开发调试必备如果你主要做Linux环境下的虚拟化开发QEMU KVM基本是标配。很多人以为KVM是虚拟机软件其实KVM只是内核模块提供了CPU虚拟化能力而QEMU负责设备模拟和用户态交互。两者配合才能组成一个完整可用的虚拟机。想深入理解这个组合可以把它称为“QEMU/KVM虚拟化开发”下面说说实际怎么用。4.1 KVM加速到底加速了什么KVM把虚拟CPU的任务交给了物理CPU执行虚拟机里的普通指令不用翻译直接跑在x86硬件上。只有涉及特权指令、内存映射、I/O操作等敏感操作时才会陷入到宿主机内核里的KVM模块处理。所以虚拟机的性能损耗主要来自设备模拟和上下文切换而不是指令翻译。这也是为什么在Linux上用KVM跑虚拟机CPU密集任务性能基本能达到物理机的95%以上。开启KVM的关键参数是-accel kvm。在启动前可以用下面的命令确认当前宿主是否支持KVMls /dev/kvm如果看不到/dev/kvm说明KVM模块没加载或者CPU虚拟化功能没开启。需要先在内核里加载kvm和kvm_intel或kvm_amd模块并且到BIOS里打开硬件虚拟化。另外在虚拟机里再开QEMU也是可以的这就叫嵌套虚拟化不过性能会再降一层不是必须不建议开。4.2 给内核开发者的QEMU调试技巧用QEMU调试内核是很舒服的一件事。你可以在宿主机上用gdb直接连到QEMU的GDB Server上在内核代码里打断点单步执行。做法是在QEMU启动时加两个参数qemu-system-x86_64 \ -kernel bzImage \ -initrd initrd.img \ -append root/dev/ram0 consolettyS0 nokaslr \ -s -S \ -nographic-s是-gdb tcp::1234的简写让QEMU启动一个GDB Server在1234端口。-S表示QEMU启动后先暂停CPU等GDB连上来继续执行。然后在宿主机上启动gdbgdb vmlinux (gdb) target remote :1234 (gdb) break start_kernel (gdb) continue这样你就能像调试普通程序一样跟踪内核启动流程。注意-append里一定要加nokaslr不然内核的地址随机化会让GDB符号对不上。这套方法我调试过好几个驱动比在物理机上用kgdb方便太多踩坑记录在后面的常见问题里一起说。4.3 性能调优CPU绑定和NUMA用KVM跑大型虚拟机时CPU绑定能明显提升稳定性。多用taskset把QEMU进程固定到某几个物理核上避免它在多个核之间频繁迁移造成缓存抖动。比如把QEMU绑到CPU 0-3上taskset -c 0-3 qemu-system-x86_64 ...如果你的宿主机是多路NUMA拓扑还可以用QEMU的-numa参数让虚拟机感知NUMA结构避免跨节点访问内存造成性能下降。不过这个参数需要结合宿主机实际拓扑来配不是所有场景都需要。我通常在虚拟机里跑大数据库或HPC任务时才做NUMA优化普通开发用默认设置就够了。5. Windows宿主机和虚拟机共享文件夹实操接下来是热词里点名要说的“Windows qemu共享文件夹命令”。先说清楚场景你的宿主机是WindowsQEMU里跑了一台Linux虚拟机你希望直接把Windows上的某个目录挂到虚拟机里不折腾网络传输。这个需求非常常见但QEMU在Windows宿主上的共享方案略麻烦我用了好几次才算摸透下面把三种主流方式对比清楚。5.1 三种共享方式怎么选第一种方式是SMB共享。如果虚拟机里跑了Samba客户端而宿主机Windows开启了文件共享服务虚拟机可以通过挂载//宿主机IP/共享目录访问文件。这种方式兼容性好Windows和Linux默认都支持但配置需要额外开防火墙、设置用户权限首次配置稍繁琐。第二种方式是9p文件系统。9p是QEMU一直带的虚拟化文件共享协议在Linux guest里挂载时要用mount -t 9p。9p的好处是简单不需要额外装服务端缺点是性能一般大量小文件操作会明显变慢。第三种方式是virtiofs。virtiofs是近年发展起来的共享文件系统专门针对虚拟机场景优化性能比9p好很多支持mmap、DAX等特性体验接近本地磁盘。但virtiofs要求guest内核比较新且宿主机QEMU版本需要有对应支持。Windows宿主上使用virtiofs需要通过VirtIO驱动配置会稍微绕一些。如果你要稳定、简单、不追求极致性能我推荐用9p。如果你需要性能且guest内核够新可以考虑virtiofs。下面分别给出能直接用的命令。5.2 用9p在Windows宿主机上共享文件夹在Windows宿主机的命令行里启动QEMU要共享D:\share_dir给虚拟机需要加如下参数qemu-system-x86_64 \ -drive fileC:\vm\disk.qcow2,ifvirtio \ -virtfs local,pathD:\share_dir,mount_taghostshare,security_modelnone,idhostshare注意路径分隔符。在Windows上QEMU能识别D:\share_dir这种写法但有时反斜杠会被解析转义更稳妥的做法是用正斜杠D:/share_dir。虚拟机启动进入Linux后先确认9p内核模块已加载然后执行mkdir -p /mnt/hostshare mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/hostshare这里的hostshare就是mount_tag要跟-virtfs参数里的mount_tag保持一致。挂载成功后/mnt/hostshare就是Windows那边的D:\share_dir。不再使用时用umount /mnt/hostshare卸载。5.3 用virtiofs获得更好的性能如果你的guest是较新的Ubuntu或Debian系统可以试试virtiofs。启动命令改为qemu-system-x86_64 \ -drive fileC:\vm\disk.qcow2,ifvirtio \ -chardev socket,idchar0,pathC:\tmp\vfs.sock \ -device vhost-user-fs-pci,queue-size1024,chardevchar0,taghostshareWindows宿主上的vhost-user配置要单独启动一个vhost-user-fs后端进程步骤相对复杂。我在Windows上实测下来9p反而更省心virtiofs更适合Linux宿主导Linux guest的环境。如果你坚持要virtiofs建议用WSL2里的QEMU配置起来比Windows原生命令行顺利不少。另外无论用哪种共享方式都建议给Windows虚拟机装VirtIO驱动否则磁盘和网络性能都上不去。VirtIO驱动的ISO镜像可以从Fedora项目官网下载然后在Windows虚拟机里安装。安装之后把磁盘控制器切到virtio网络适配器切到virtio-net共享参数才能正常识别。6. 踩坑记录与日常运维心得最后这部分是我自己折腾QEMU以来总结的避坑经验很多问题不值一提但卡住的时候真的很浪费时间。整理成几条速查表供你对照排查。6.1 启动闪退、窗口黑屏和剪贴板问题QEMU启动后马上退出最常见原因是-accel指定的加速后端不可用。比如在Windows上用WHPX但Hyper-V没开启或者在Linux上指定了KVM/dev/kvm又不存在。优先检查加速模块移除-accel参数再试确认能跑之后再逐步加回来。窗口黑屏通常是因为把显示输出给了-nographic但操作系统没有串口控制台。如果你用图形界面不要加-nographic如果要用串口终端确保Linux内核启动参数里有consolettyS0否则串口没有输出看起来就像“死机”。我曾经用默认内核启动ARM64虚拟机死活等不到登录提示后来才发现是console没配置。剪贴板和拖拽在QEMU里不像VMware那样开箱即用。配合SPICE协议加-spice相关参数或者安装QEMU guest agent之后才可能实现双向剪贴板共享。日常开发我一般直接放弃剪贴板用SSH加scp反而更效率。这个取舍看个人习惯。6.2 磁盘膨胀和快照误删qcow2镜像用久了会越来越大哪怕你在虚拟机里删了很多文件宿主上的qcow2文件也不会自动缩小。想回收空间用下面的命令压缩qemu-img convert -O qcow2 disk.qcow2 disk.compact.qcow2它会生成一个新的紧凑镜像但需要额外磁盘空间。我习惯压缩前先看下当前镜像大小避免目标盘被撑满。压缩完成后再做数据完整性检查。快照功能也很容易误操作。创建快照用qemu-img snapshot -c clean_state disk.qcow2回滚快照用qemu-img snapshot -a clean_state disk.qcow2但要记住快照不是靠谱的备份手段。如果磁盘损坏或误删快照数据照样会丢。重要数据建议定期用qemu-img convert导出一份完整镜像再做异地备份。6.3 我自己的几条实操体会第一永远先确认QEMU版本。很多参数在不同版本之间行为不一样比如-netdev user的hostfwd在旧版本里大小写要求不同升级之后可能悄悄变化。用qemu --version查一下心里有底。第二跨架构模拟启动失败时先把参数精简到最少。不带磁盘、不带网络只留-M、-cpu、-kernel、-nographic跑通了再一样一样加。这样定位问题会快非常多。我调试ARM64启动时整整半小时都以为是内核不对最后发现是多传了一个不兼容的-device参数。第三Windows宿主导师命令的编码问题。在PowerShell里执行QEMU命令时路径里的反斜杠、分号、逗号都有可能会被当作特殊字符。最省心的方法是把QEMU启动命令写成一个.bat脚本用正斜杠写路径再用Call调用很大程度上能避开各种诡异的解析问题。共享文件夹相关的命令最好也放脚本里方便反复调试。QEMU这套工具一开始确实有点门槛但一旦把参数体系搞清楚你会发现它比商业虚拟化软件更自由、更适合搞开发。遇到问题了多看日志多减参数多查版本大部分坑都是可以填平的。后面如果时间充裕我还可以写一篇在QEMU里跑GUI应用和USB设备重定向的内容那是另一个非常有用的方向。