
我经常被人问到“QEMU虚拟机和宿主机之间怎么传文件”这个问题看起来基础但实际踩的坑比想象中多得多。不管你是搞嵌入式交叉编译、内核调试还是单纯在宿主机和arm64虚拟机之间倒腾构建产物文件传输都是绕不开的一环。网上的资料零零散散要么只讲一种方法要么直接甩一句“用9p”结果新手配了半天全是坑。我花了不少时间把这几种主流方案都实测了一遍包括通过内置网络栈直接下载、挂载本地目录、用SSH服务做桥接以及一个性能开销很小的共享目录方案。这篇文章把它们整理成一套可以直接照抄的实操手册每一种方法的适用范围、网络配置以及我踩过的坑都会讲清楚。1. 内容整体设计与思路拆解1.1 为什么传个文件这么费劲QEMU本身是一款纯软件模拟的虚拟化方案有别于KVM这种硬件辅助虚拟化它模拟出来的网卡、磁盘、串口对宿主机来说都是真实存在的设备。然而QEMU的默认配置是“与世隔绝”——虚拟机和宿主机之间并没有像VMware那样的共享文件夹菜单也不会自动分配一个宿主内网IP给你。这就导致一个尴尬的局面很多人在qemu-system-aarch64启动虚拟机之后发现虚拟机里看不到宿主机上的文件只能用U盘镜像一点点拷效率极低。实际上从QEMU 2.0开始它已经内置了多种与宿主机通信的机制原理上可以分为三类一是网络层让虚拟机通过一种叫“用户模式网络栈”的方式访问宿主机宿主机可以伪装成一个网关IP供虚拟机访问这种方式的优点是不需要root权限缺点是虚拟机默认无法直接从外部访问进来。二是设备层把宿主机的一个目录直接当成块设备或者9p文件系统挂载进虚拟机。特点是快、直接但需要内核开启对应模块。三是外设层诸如virtio-serial这类字符设备适合传输小文件和控制信号不属于大文件传输的主流方案。1.2 方案选型先看场景再动手在我实际测试下来不存在“一招鲜”的方案。选哪个方法取决于你的虚拟机的用途、有没有图形界面以及你用的是不是定制内核如果你只是偶尔传几个配置文件、内核镜像追求最简单推荐用网络下载方式方案A宿主机开一个临时HTTP服务虚拟机里用wget几乎零配置。如果你需要频繁交换代码、构建产物且你的内核开启了9p支持推荐用共享目录方式方案D体验上接近VMware的共享文件夹但对内核配置有要求。如果你在调试内核、bootloader经常需要替换启动镜像推荐用直接挂载镜像文件的方式方案C不用起系统就能往文件系统里塞东西。如果你已经有一个跑起来的Linux虚拟机环境想在运行中传文件最稳的是SSH方案方案B走TCP端口转发稳定且不挑内核。下面的完整流程我会按照从简单到复杂的顺序来写你可以按需取用。2. 方案A最快速的“宿主机开HTTP服务虚拟机wget下载”这个方法适合一次性传文件比如把交叉编译好的内核镜像、rootfs压缩包丢给虚拟机。原理很简单QEMU用户态网络栈SLIRP会把虚拟机放在一个独立的子网里默认10.0.2.0/24虚拟机看到的宿主机网关是10.0.2.2。也就是说宿主机上监听某个端口的服务虚拟机里可以通过10.0.2.2访问到。2.1 宿主机上的操作假设我的工作目录是~/qemu_share里面有文件kernel.img和rootfs.ext4宿主机是LinuxWindows/macOS也类似只是Python命令略有差异cd ~/qemu_share python3 -m http.server 8000这里用Python自带的HTTP服务监听在0.0.0.0的8000端口。如果文件比较大也可以用python3 -m http.server 8000 --bind 0.0.0.0注意默认情况下它监听所有网卡如果你本机有公网IP这么做会有安全隐患建议只用--bind 127.0.0.1。不过QEMU的slirp网络比较特殊从虚拟机访问10.0.2.2时本质上是QEMU进程在宿主侧发起的连接所以即使服务只绑定在127.0.0.1虚拟机也能访问到。这是实测过的结论放心用。2.2 虚拟机里的操作在虚拟机内部假设已经启动了Linux系统wget http://10.0.2.2:8000/kernel.img curl -O http://10.0.2.2:8000/rootfs.ext4如果你是Windows虚拟机浏览器里直接访问http://10.0.2.2:8000也能看到文件列表。这个方法还有一个变种如果宿主机上有rsync可以在宿主机上用rsync daemon模式虚拟机里用rsync客户端同步目录增量传输大目录时效率更高。不过对于一次性传输HTTP服务已经足够简单了。2.3 注意事项与心得第一如果虚拟机里wget提示连接拒绝先ping一下10.0.2.2是否通。如果不通检查QEMU启动参数里是否加了-netdev user以及虚拟机内部网卡是否配置了DHCP。第二传大文件时建议在宿主机终端加--bind 127.0.0.1避免局域网内其他人也能访问到你的文件。第三这个方法理论上限就是单文件传输无法实现双向同步。想双向同步得靠方案B或方案D。3. 方案B通过SSH端口转发实现稳定互传如果你的虚拟机里已经装了SSH服务那么恭喜你最可靠的传输通道已经就绪了。思路是QEMU用户态网络里虚拟机默认是“出不去也进不来”的但我们可以通过hostfwd参数把宿主机某个端口的所有流量转发到虚拟机里的22端口。这样宿主机用ssh连接本机的2222端口就等价于连接虚拟机的22端口。3.1 启动QEMU时添加端口转发qemu-system-aarch64 \ -M virt \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -kernel kernel.img \ -drive filerootfs.ext4,formatraw,ifvirtio \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevnet0这里关键参数是hostfwdtcp::2222-:22意思是把宿主机上的TCP 2222端口转发到虚拟机里的22端口。注意后面那个22前面有一个冒号那个冒号前的空字段代表虚拟机内部IP留空即匹配虚拟机的任意IP。3.2 宿主机通过scp/rsync传输文件启动之后在宿主机上ssh -p 2222 user127.0.0.1 scp -P 2222 ~/qemu_share/kernel.img user127.0.0.1:/home/user/ rsync -avz -e ssh -p 2222 ~/qemu_share/ user127.0.0.1:/home/user/share/如果你经常这么搞建议在~/.ssh/config里加一段Host qemu-arm64 HostName 127.0.0.1 Port 2222 User root StrictHostKeyChecking no UserKnownHostsFile /dev/null以后就可以直接ssh qemu-arm64省去一堆参数。3.3 如果虚拟机里没有SSH服务怎么办有些精简rootfs里没有sshd可以临时用方案A先把openssh-server的安装包装进虚拟机# 在虚拟机里假设用的是deb系系统 apt update apt install openssh-server -y systemctl enable ssh systemctl start ssh如果是用buildroot自己做的rootfs最好在配置阶段选中openssh这样最省事。3.4 实测踩坑端口转发不生效最常见的坑是hostfwd参数写错。网上很多例子写的是hostfwdtcp:127.0.0.1:2222-:22注意旧版QEMU接受这种写法新版7.0以上建议写成hostfwdtcp::2222-:22。如果加了IPv6地址绑定还可能遇到socket bind失败此时检查一下宿主机的防火墙是否放行了2222端口。还有一点QEMU的user网络模式下虚拟机默认网关是10.0.2.2DNS是10.0.2.3。如果虚拟机里ssh能连上但apt更新很慢不妨检查/etc/resolv.conf里是否配置了10.0.2.3。4. 方案C直接修改磁盘镜像文件离线传输法这个方法适合在虚拟机“关机”状态下传输文件。原理非常简单把虚拟机的磁盘镜像raw格式或者qcow2格式直接挂载到宿主机的文件系统里然后把文件拖进去或拖出来。相当于你拿读卡器直接读写一张SD卡根本不需要启动操作系统。4.1 raw格式镜像的挂载方法如果你用的是raw格式的rootfs.ext4那就太简单了sudo mkdir -p /mnt/qemu_rootfs sudo mount -o loop,offset1048576 rootfs.ext4 /mnt/qemu_rootfs这个offset1048576经常有人卡住因为很多人不清楚rootfs.ext4里第一个分区之前可能有对齐填充。如果你用fdisk -l rootfs.ext4查看会发现类似这样的输出Device Boot Start End Sectors Size Id Type rootfs.ext4p1 2048 524287 522240 255M 83 LinuxStart为2048每个扇区512字节所以分区起始字节偏移就是2048×5121048576。如果不加offset直接mount会报错“wrong fs type, bad option, bad superblock”因为镜像开头是分区表而不是ext4超级块。如果你的镜像有多个分区还可以用kpartx工具自动映射sudo apt install kpartx -y sudo kpartx -av rootfs.ext4 # 输出类似add map loop0p1 ... sudo mount /dev/mapper/loop0p1 /mnt/qemu_rootfs用完以后卸载并删除映射sudo umount /mnt/qemu_rootfs sudo kpartx -dv rootfs.ext44.2 qcow2格式镜像的挂载方法qcow2格式比raw多了一层写时复制的封装不能直接用mount -o loop挂载需要借助qemu-nbd工具sudo modprobe nbd max_part8 sudo qemu-nbd --connect/dev/nbd0 disk.qcow2 sudo fdisk -l /dev/nbd0 sudo mount /dev/nbd0p1 /mnt/qemu_rootfs用完以后sudo umount /mnt/qemu_rootfs sudo qemu-nbd --disconnect /dev/nbd04.3 实测心得与坑这个方法最大的坑是千万不要在虚拟机运行的时候挂载它的镜像否则文件系统会损坏。我自己就干过这种糊涂事结果虚拟机里fsck一片红教训惨痛。还有一点要注意如果你修改了rootfs里的内容比如替换了某个动态库最好先在宿主机上验证一下文件的权限和属主。因为宿主机上你用sudo挂载创建的文件owner通常是root但虚拟机里某些服务可能以普通用户身份运行直接复制过去可能导致权限不对启动报错。这个方案适合向镜像里“注入”文件也适合从镜像里“提取”文件。比如我用它来给buildroot构建的rootfs预先塞入wpa_supplicant配置省得登录虚拟机后再折腾。5. 方案D共享目录VirtFS/9p实现双向实时同步这个方案是最接近VMware共享文件夹体验的虚拟机里直接挂载宿主机某个目录两边文件实时同步。它的原理是QEMU通过virtio-9p设备把宿主机的目录直接暴露给虚拟机虚拟机侧通过9p文件系统协议访问。性能开销比网络方案低因为它绕过了TCP/IP协议栈直接走virtio通道。5.1 宿主机启动参数qemu-system-aarch64 \ -M virt \ -cpu cortex-a72 \ -smp 4 \ -m 4096 \ -kernel kernel.img \ -drive filerootfs.ext4,formatraw,ifvirtio \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0 \ -virtfs local,path/home/user/qemu_share,mount_taghostshare,security_modelnone,idshare0关键参数是最后一行。path是宿主机要共享的目录mount_tag是虚拟机里挂载时用到的标签security_modelnone表示不进行权限映射直接把宿主机的UID/GID暴露给虚拟机。如果你想做权限映射可以改成security_modelmapped-xattr但性能会下降而且要求虚拟机的文件系统支持user_xattr。5.2 虚拟机里挂载在虚拟机内部以root身份mkdir -p /mnt/host mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/host如果挂载时报错“mount: unknown filesystem type 9p”说明内核没开9p支持。要么重新编译内核打开CONFIG_NET_9Py、CONFIG_9P_FSy要么改用其他方案。挂载成功后/mnt/host就是宿主机/home/user/qemu_share目录的实时镜像。在虚拟机里写文件宿主机立即可见反过来也一样。5.3 Windows虚拟机也能用吗Windows虚拟机没法直接挂载9p目录除非你装第三方驱动。如果你是在Windows虚拟机里跑QEMU想要共享文件推荐两个思路一是用SMB共享宿主机开Samba服务Windows虚拟机通过\10.0.2.2\share访问。这个方法稳定但配置Samba稍麻烦。二是用QEMU自带的内置SMB功能不过它依赖宿主机安装smbd且仅限Windows guest实测不如直接方案A方便。5.4 实测性能与注意事项我在arm64虚拟机上用dd测试过9p的写性能大概能到200MB/s以上读性能略低一些和网盘方案完全不是一个量级。但开9p有一个隐患宿主机目录里的文件变更事件inotify不会自动同步到虚拟机。如果你在虚拟机里跑一个监听inotify的构建工具比如webpack它可能感知不到宿主机侧的文件变化。这点和VMware的HGFS不太一样算是9p的一个短板。另外如果共享目录里的文件数量非常多比如上百万个9p的目录遍历性能会比较差因为每个目录项都需要一次协议交互。建议只共享小规模、少层级的目录别把整个home目录共享进去。6. 综合对比与选型建议下面这张表是我根据自己的使用体验整理的可以直接对照着选方案传输方向是否需要客户端性能配置复杂度推荐场景HTTP wget宿主机到虚拟机虚拟机有wget/curl即可中等极低一次性传文件SSH/scp双向虚拟机有sshd中等低运行中双向互传挂载镜像双向宿主机有mount权限高中离线修改镜像内容VirtFS/9p双向虚拟机内核支持9p高中高频繁实时共享目录我的个人倾向是日常开发调试用SSH方案构建产物通过scp或rsync同步如果只是往rootfs里塞东西用方案C直接挂载镜像只有当你需要在宿主机和虚拟机之间频繁编辑同一批源码文件时才值得折腾9p。7. 其他虚拟化工具的做法参考虽然主题是QEMU但既然很多人会拿VMware Workstation做对比我多说一句VMware Workstation的共享文件夹功能HGFS确实是同类里最省心的装好vmware-tools之后直接在虚拟机设置里加一个目录就行。但它有一个坑“防止虚机文件丢失”的选项在快照回滚时可能会让人误以为文件丢了——其实HGFS指向的宿主机目录并没变只是快照状态里看到的挂载点没恢复。VirtualBox的共享文件夹方案则是通过vboxsf文件系统实现体验类似9p但Windows虚拟机也需要安装增强功能。每个方案都有它的脾气QEMU的传文件方式虽说要手动配置但胜在可控性强适合做自动化脚本封装。8. 实战运维技巧与常见问题速查8.1 一条命令搞定宿主机到虚拟机的文件传输如果你只是想在启动QEMU前把一个文件注入到rootfs又不想开虚拟机可以直接在宿主机上用debugfs操作ext4镜像sudo debugfs -w -R write /path/to/hostfile /guest/path/file rootfs.ext4这个方法比挂载还快但它只支持ext系列文件系统qcow2镜像需要先转换为raw并且对文件碎片大的情况效率不高。偶尔用一次可以频繁操作建议还是挂载。8.2 虚拟机与宿主机之间复制粘贴失效的排查这个问题在QEMU里通常不是因为没装增强工具而是因为剪贴板属于GUI前端比如virt-manager的SPICE管理的功能和文件传输无关。如果你在虚拟机里执行hostname能通、网络正常但复制粘贴失败优先检查SPICE agent是否在虚拟机内运行systemctl status spice-vdagentdWindows虚拟机则要确认是否安装了spice-guest-tools。8.3 虚拟机上次意外关机后无法挂载镜像的恢复QEMU虚拟机如果没走优雅关机流程比如kill -9 qemu进程镜像文件里可能残留未落盘的日志。这时直接mount -o loop会报错建议先用e2fsck修复sudo e2fsck -f rootfs.ext4如果是qcow2先qemu-nbd连接后再fsck /dev/nbd0p1。我遇到过好几次因为宿主机突然断电导致虚拟机rootfs损坏这个命令几乎是必备技能。8.4 网络方案里虚拟机无法上网的排查顺序如果你启动QEMU后虚拟机里ping不通10.0.2.2按这个顺序查第一确认启动参数里有没有-netdev user和-device virtio-net-pci两者是配套的缺一不可。第二确认虚拟机内核编译选项里有没有打开CONFIG_VIRTIO_NETy。很多精简内核把virtio网卡驱动编成了模块而rootfs里又没有对应模块导致网卡识别了但无法工作。第三确认虚拟机内有没有启用DHCP客户端。用busybox的udhcpc发行版需要手动运行udhcpc eth0。8.5 快速判断当前使用哪种传输方式有时候你接手别人的一个QEMU环境不确定现在用的哪种方案可以这样快速识别虚拟机里能访问10.0.2.2说明用了user模式网络方案A/B可行。虚拟机里有一个/mnt/host目录且mount输出里有9p字样说明用了方案D。宿主机上看到一堆loop设备和nbd设备说明有人用过方案C。这种“先看现象再定位”的思路比死记硬背配置更有用。9. 我在实际使用中的体会折腾QEMU文件传输这几年我最深刻的体会是方法本身都不难难的是搞清楚自己到底处于什么网络拓扑、什么文件系统环境里。如果你是在做一个快速验证的原型机别浪费时间去调9p参数HTTP方案三分钟搞定。如果你要长期维护一个虚拟环境SSH方案可以说是一劳永逸还能顺带获得端口转发、tcpdump抓包这些额外能力。如果你经常更换rootfs或者内核学会挂载镜像文件能让你在几分钟内完成原本需要启动两三次虚拟机才能完成的操作。最后分享一个小技巧不管用哪种方案建议在宿主机和虚拟机里都写一个自动化的同步脚本比如宿主机侧的push.sh和虚拟机侧的pull.sh。把常用的hostfwd端口、共享目录路径、scp参数都固化到脚本里这样你每次新建虚拟机时不需要重复思考直接执行脚本就行。我自己的脚本已经用了一年多换了四五次rootfs传文件这件事再也没成为开发流程的瓶颈。