QEMU与宿主机传文件全攻略:共享目录、网络传输、磁盘挂载实战

发布时间:2026/10/1 9:25:30
QEMU与宿主机传文件全攻略:共享目录、网络传输、磁盘挂载实战
我最初折腾QEMU不是被启动参数难倒的是被传文件这件事卡了整整一个下午。镜像装好了、系统能开机了结果发现代码在宿主机上虚拟机里却拿不到。想拖拽文件进去没反应开共享文件夹不知道参数问了一圈才知道QEMU传文件根本不像VMware那样开箱即用。这篇文章就把我在QEMU和宿主机之间传文件的完整实践经验总结出来覆盖临时小文件、开发调试、大块数据迁移这几类真实需求。每个方案我都会讲清楚原理、适用场景、完整操作步骤和踩过的坑照着做基本能一次跑通。1. 先搞清楚你要传的是什么再决定用哪种方案很多人一上来就打开搜索引擎找QEMU传文件命令然后被一堆术语劝退。其实问题不在命令有多难而是你压根没想清楚自己要传的东西属于哪一类。1.1 三种典型需求临时文件、开发调试、大块数据我把日常需求粗暴分成三类。第一类是临时小文件。比如从宿主机给虚拟机丢一个安装包、一份PDF、一个几十MB的日志。这类需求追求的是省事最好一条命令解决不想为了传个文件去改虚拟机的启动配置。第二类是开发调试场景。你正在写代码或者跑编译任务需要频繁在宿主机和虚拟机之间同步文件。这类需求追求的是效率和自动同步每次手动拷贝会疯掉最好建一条稳定的双向通道。第三类是大块数据。比如你要把几十GB的磁盘镜像、数据库备份、数据集搬进虚拟机或者反过来从虚拟机里导出来。这类需求如果还走网络或者共享目录速度会让人崩溃而且动不动就中断必须用更底层的办法。1.2 方法全景图先知道有哪些路可以走QEMU传文件本质上只有四条路共享目录让虚拟机和宿主机共享同一个文件夹类似VMware的共享文件夹功能对应9p和virtiofs协议。网络传输把虚拟机当成一台普通机器通过SSH、scp、rsync、HTTP等方式传文件需要先配置好网络。磁盘镜像操作直接在宿主机层面操作虚拟机的磁盘镜像文件适合大块数据和整个磁盘级别的迁移。外设通道模拟USB存储设备、剪贴板共享等适用于少量特定场景。每个方案都有它的甜点区也有它的大坑。比如9p兼容性好但性能一般virtiofs性能好但对内核有要求网络传输方案灵活但需要先解决网络配置问题。下面我把每个方案都拆开了讲。2. 共享目录实战9p和virtiofs的选型、配置与排查共享目录是大多数人最先想到的方案因为物理机上我们早就习惯了某个文件夹两边都能访问这种模式。QEMU里这套东西做是能做但很多人的第一反应是怎么挂不上去2.1 两者的底层差异与性能差异9p是Plan 9遗产几十年前的设计QEMU一直内置支持。它的好处是通用Linux内核默认编译了9p的驱动模块基本不用额外装东西。但9p协议设计得太早没有针对现代硬件做过优化大量小文件操作时延迟明显遇到目录层级深一点的文件树性能会断崖式下跌。virtiofs是后起之秀专门为虚拟机共享文件设计的半虚拟化方案。它把共享目录的一部分页缓存直接映射给虚拟机所以大文件顺序读写的性能非常接近本地磁盘。我实测在同一个机器上跑内核编译virtiofs比9p快了一倍不止缓存命中后的随机读延迟也低很多。但是virtiofs有两个硬性前提宿主机内核要支持virtiofsd一般Linux发行版内核都自带但需要额外安装virtiofsd这个守护进程虚拟机内核也要支持virtiofs协议而且启动参数要指定-fsdev和-mount tag。如果你是老内核比如CentOS 7默认的3.10内核virtiofs基本是没指望的老老实实用9p。2.2 命令行启动参数与挂载示例先讲我实际用过的9p启动方式。假设宿主机上要共享的目录是/data/share虚拟机挂载到/mnt/shareQEMU命令大致长这样qemu-system-x86_64 \ -m 4096 \ -smp 4 \ -drive file/path/to/disk.qcow2,ifvirtio \ -fsdev local,idshared,path/data/share,security_modelnone,multidevsremap \ -device virtio-9p-pci,fsdevshared,mount_taghostshare \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0解释几个关键参数security_modelnone宿主机不检查文件权限差异所有文件以当前用户的权限呈现给虚拟机。这是最省事也最常用的方式但要注意如果你以root用户启动了QEMU虚拟机里的用户就能拿到宿主机上root能读写的文件有安全风险。multidevsremap这是个容易忽略的参数。如果共享目录里包含多个设备挂载点比如你在/data/shares下面又挂了一个NFS不设置multidevsremap的话9p可能因为inode冲突报duplicate ownership错误。加上这个参数后QEMU会重新映射多个设备的inode避免冲突。虚拟机内部挂载命令sudo mkdir -p /mnt/share sudo mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/share注意挂载源是hostshare对应的就是QEMU启动参数里的mount_tag两边错了任何一个都挂不上。如果要用virtiofs命令稍有不同。首先确保宿主机装了virtiofsd# Debian/Ubuntu sudo apt install virtiofsd # RHEL/CentOS/Fedora sudo dnf install virtiofsd然后启动QEMU时用qemu-system-x86_64 \ -m 4096 -smp 4 \ -drive file/path/to/disk.qcow2,ifvirtio \ -chardev socket,idchar0,path/tmp/virtiofs.sock \ -device vhost-user-fs-pci,queue-size1024,chardevchar0,taghostshare \ -object memory-backend-file,idmem,size4096M,mem-path/dev/shm,shareon \ -numa node,memdevmem \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0看到没virtiofs除了要指定chardev和vhost-user-fs-pci之外还要求虚拟机内存改成共享内存后端memory-backend-file否则vhost-user设备起不来。这个细节我当初折腾了很久后来才在virtiofs官方文档里看到。虚拟机内部挂载sudo mkdir -p /mnt/share sudo mount -t virtiofs hostshare /mnt/share这里不需要指定transvirtio因为virtiofs本身就是标准virtio设备挂载源同样是mount_tag。2.3 virt-manager图形界面配置如果不想记命令行参数用virt-manager图形界面也能配共享目录。选中虚拟机打开显示细节里的IDE/SATA/USB控制器或者内存和CPU旁边的添加硬件选项找到Filesystem设备。在device type里选virtiofs或9p填写source path宿主机目录和target path挂载点即mount_tag。virt-manager会自动把命令行参数拼出来不用你手工处理。但注意你如果在virt-manager里选了virtiofs它也强制要求虚拟机内存是共享内存。virt-manager一般会自动改但如果之后你手动调整过内存设置要把共享内存勾选上不然启动虚拟机时会报the memory backend must be shared。2.4 挂载失败排查权限、缓存模式、SELinux共享目录挂载失败90%的情况是下面几个原因第一个是security_model选错导致虚拟机内读写权限异常。如果你用security_modelmapped-xattr宿主机上的文件会被打上扩展属性来模拟权限但很多文件系统比如FAT、某些网络盘不支持这些xattr挂载后创建文件会报Operation not supported。实在搞不定权限直接用security_modelnone最省心。第二个是SELinux捣乱。宿主机开了SELinux enforce模式虚拟机通过9p/virtiofs访问的文件可能会被SELinux策略拦截表现是Permission denied但权限明明是够的。临时验证就执行sudo setenforce 0想长期解决就配置SELinux布尔值或者干脆给共享目录加z标签sudo chcon -Rt svirt_sandbox_file_t /data/share第三种是缓存模式。9p默认的缓存策略对某些应用不友好比如数据库类应用需要看到文件内容实时变化不加缓存又会慢。9p支持cacheloose、cachemmap等模式。我一般命令行直接加sudo mount -t 9p -o transvirtio,version9p2000.L,cacheloose hostshare /mnt/sharecacheloose不做强一致保证但换来了吞吐量。如果你在虚拟机和宿主机两边同时频繁改同一个文件建议别开loose宁可慢一点否则可能读到过期数据。2.5 实际性能测试别被虚拟文件系统骗了我专门拿一个约8GB的小型代码仓库做过对比。9p模式下git status在虚拟机里跑居然要40多秒virtiofs模式下十几秒能出来虽然还是比本地磁盘慢不少但体感差异非常明显。如果是编译内核这种极端场景9p会卡到让人怀疑机器是不是死机了。所以我的建议是能用virtiofs就用virtiofs内核太老才退回到9p。如果你只是传几个配置文件那这两个方案都足够没必要纠结性能。3. 走网络这条路QEMU内的SSH、scp与rsync共享目录虽然方便但在某些场景下你会发觉它不够用。比如你想测试的是真实的网络环境或者你用的是Windows虚拟机、BSD系统9p和virtiofs不一定都能跑。这时候把虚拟机当成一台普通机器走网络传文件才是通用的做法。3.1 用户网络与端口转发配置QEMU默认的网络模式是用户模式user mode虚拟机的网卡通过QEMU内置的NAT访问外部网络。这种模式下宿主机访问虚拟机需要做端口转发。启动虚拟机时一般这样配置qemu-system-x86_64 \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevnet0hostfwdtcp::2222-:22的意思是宿主机上监听2222端口的所有TCP连接全部转发到虚拟机网卡的22端口。这样你在宿主机上执行ssh -p 2222 userlocalhost就能连进虚拟机。关键点QEMU的用户模式网络自己的网段默认是10.0.2.0/24虚拟机一般拿到的IP是10.0.2.15网关是10.0.2.2DNS是10.0.2.3。这些都可以修改但默认值非常固定。如果虚拟机里ip addr看到的地址不是这个网段说明你用了其他网络模式。3.2 桥接网络与直接访问如果你想让虚拟机和宿主机处于同一个局域网虚拟机从路由器获取IP宿主机之间可以直接用内网IP互访那就用桥接模式。创建桥接接口Linux下通常用brctl或ip命令管理麻烦一点然后QEMU参数改为qemu-system-x86_64 \ -netdev bridge,idnet0,brbr0 \ -device virtio-net-pci,netdevnet0前提是宿主机的物理网卡已经加到了br0这个网桥上。桥接模式下虚拟机就像是局域网里另一台真机SSH、scp、rsync、HTTP全部走真实网络路径传文件时的吞吐量一般比用户模式高延迟也小。但就是配置麻烦天生依赖宿主机网络环境笔记本换Wi-Fi后桥接经常不通。我自己的习惯是日常用用户模式端口转发只有需要高带宽传输时才切桥接。3.3 scp/rsync常用命令与调优网络通了之后传文件其实就到了Linux基础应用层面。单次拷贝用scp就够了scp -P 2222 /data/bigfile.tar.gz userlocalhost:/home/user/注意scp用-P指定端口和ssh一样-p是保留时间戳的别记混了。端口映射那里hostfwdtcp::2222-:22已经绑定了宿主机的2222端口所以这里也要带上-P 2222。如果文件很大scp可能会断我的建议是用rsync。rsync可以断点续传而且只传变化的部分。典型用法rsync -avzP -e ssh -p 2222 /data/bigfile.tar.gz userlocalhost:/home/user/-a保留属性-v显示详细输出-z在传输时压缩-P表示显示进度且支持断点续传。对于大目录同步rsync基本是标准答案。网络传文件最常见的坑是防火墙。虚拟机里的sshd没装或者没启动宿主机上ssh -p 2222 userlocalhost会显示Connection refused。检查顺序是虚拟机里systemctl status sshd再ss -tlnp | grep 22看sshd在不在监听然后看宿主机防火墙是否放行了2222端口。3.4 单向复制的坑与自定义端口映射端口转发有一个隐藏坑如果你在宿主机上同时运行多个QEMU虚拟机都映射了2222端口第二个虚拟机必然失败。解决办法是每个虚拟机映射不同端口比如第一台映射2222第二台映射2223。另外hostfwd实际上还支持从虚拟机访问宿主机端口的反向映射。QEMU默认虚拟机可以访问宿主机的10.0.2.2也就是宿主机在用户网络里的网关地址。但如果你想通过宿主机端口访问宿主机上的服务比如宿主机起了个HTTP服务在8080虚拟机里直接访问10.0.2.2:8080是通的不需要额外配置。这个机制在开发联调时很常用我经常把宿主机上的mock服务供虚拟机调用。4. 大块头怎么办镜像转换、磁盘扩容与宿主机直接读写共享目录和网络传输解决的是文件层面但有些需求是磁盘层面。比如你要把整个虚拟机的数据复制出来或者要给虚拟机磁盘扩容或者你的虚拟机里没有网络、没法用共享目录只能从宿主机往raw格式的磁盘镜像里直接塞文件。这时候就得操作虚拟机磁盘镜像文件本身。4.1 qcow2转raw与raw转qcow2QEMU的磁盘镜像默认是qcow2格式支持快照、压缩、按需分配不用的空间不会占满宿主机的磁盘。但有的时候比如你要把虚拟机迁移到另一个不支持qcow2的环境就得转成raw。转换命令相当简单qemu-img convert -f qcow2 -O raw disk.qcow2 disk.raw反过来qemu-img convert -f raw -O qcow2 disk.raw disk.qcow2qemu-img convert的智能之处在于它会按内容复制数据不是逐扇区复制。所以如果你在qcow2里删除了大量文件转换成raw后再转回qcow2镜像会明显变小。我遇到过几次这种情况转换一次能减肥20%到30%。4.2 宿主机上挂载qemu镜像直接读写内部文件有些场景你不想启动虚拟机但想从宿主机直接读虚拟机磁盘里的文件。比如虚拟机崩溃了你想抢救数据或者不想走网络就想一次性把一堆文件塞进虚拟机的根文件系统。对于raw格式镜像可以用Linux的loop设备直接挂载sudo losetup /dev/loop0 disk.raw sudo partprobe /dev/loop0 sudo mount /dev/loop0p1 /mnt/vmdiskN.B.如果磁盘里只有一个分区挂载点是/dev/loop0p1如果有多个分区/dev/loop0p2、/dev/loop0p3以此类推。分区表是GPT还是MBR会影响partprobe的结果但基本都通用。对于qcow2格式不能直接losetup需要先转换成raw或者用qemu-nbd挂载sudo modprobe nbd max_part8 sudo qemu-nbd --connect/dev/nbd0 disk.qcow2 sudo mount /dev/nbd0p1 /mnt/vmdisk挂载完成后你就可以直接用cp、rsync等命令往虚拟机磁盘里塞文件。操作完成后要记得先卸载再断开nbd连接sudo umount /mnt/vmdisk sudo qemu-nbd --disconnect /dev/nbd0这套操作我用于备份、恢复和离线修改虚拟机系统配置。唯一要注意的是如果虚拟机不是关机状态此时磁盘里可能有未落盘的数据直接操作会导致文件系统损坏所以怀孕前一定要确保虚拟机处于关机状态。4.3 磁盘扩容给虚拟机换个大硬盘另一个经常被问到的需求是扩容。虚拟机磁盘用着用着空间不够了不想重建镜像那就扩容。qcow2可以用qemu-img resizeqemu-img resize disk.qcow2 50G这条命令只是把磁盘镜像容量变成原来的50G但分区表不会自动变。你还需要进入虚拟机对新空间进行分区和文件系统扩容。如果你用LVM过程相对简单使用pvresize扩展PV再lvextend扩展LV最后resize2fs或xfs_growfs扩展文件系统。经验之谈如果你要扩容的是根文件系统且里面跑着重要服务我强烈建议先做快照。qcow2支持内置快照qemu-img snapshot -c before-resize disk.qcow2扩容出问题随时可以回滚qemu-img snapshot -a before-resize disk.qcow2我就在一次扩容中把分区的起始偏移搞错了导致虚拟机起不来。还好有快照几分钟就恢复了不然重建系统会让人崩溃。5. 那些零散的传输方式剪贴板、USB和spice通道共享目录和网络已经能覆盖绝大多数情况但总有些场景不按常理出牌。比如Windows虚拟机想和宿主机共享剪贴板或者你想在宿主机和虚拟机之间拖拽文件。这些零碎需求其实也有对应方案。5.1 SPICE协议与剪贴板共享如果你用的是协议栈SPICE默认的QXL显示协议在性能上不如SPICE好用剪贴板共享是可以在图形界面里直接配的。启动QEMU时加上qemu-system-x86_64 \ -spice port5930,disable-ticketing \ -vga qxl然后用virt-viewer或者remote-viewer连接remote-viewer spice://localhost:5930这时宿主机和虚拟机的剪贴板共享通常是直接生效的。比如在虚拟机里复制一串命令切回宿主机就能CtrlV粘贴到终端。如果没生效检查一下虚拟机里有没有装spice-vdagentsudo apt install spice-vdagentspice-vdagent这个服务还负责实现拖拽文件功能部分发行版支持也负责自动调整分辨率。所以遇到QEMU虚拟机分辨率怎么改大的问题先怀疑是不是没装spice-vdagent。5.2 USB设备直通有些场景你想把宿主机上插的一个U盘、一块移动硬盘直接插进虚拟机。这属于外设直通不是严格意义上的传文件但确实是很多人拿来传文件的办法。QEMU支持USB直通最简单的是qemu-system-x86_64 \ -usb -device usb-host,vendorid0x1234,productid0x5678vendorid和productid你可以先用lsusb查到然后把对应id填进去。启动虚拟机后U盘就直接出现在虚拟机里了像插在本机一样。这个方案对宿主机上有没有驱动完全无感因为USB设备是被虚拟机看到的。缺点是同一时间只能有一端能访问它还有一个更麻烦的坑是如果宿主机上某个进程正在占着这个设备比如文件管理器在扫描虚拟机里会显示设备被占用。5.3 virtio-win驱动Windows虚拟机必备聊到Windows虚拟机传文件有个东西绕不开virtio-win驱动包。Windows不自带virtio驱动如果你用virtio磁盘、virtio网卡或者virtiofsWindows默认系统里是没有对应驱动的设备管理器里全是黄色感叹号网络和磁盘根本起不来。解决方法是在装Windows时用-cdrom virtio-win.iso挂载virtio-win驱动ISO安装时选择加载驱动或者在装完系统后用这个ISO补装。virtio-win里也包含spice-guest-tools装了之后剪贴板共享和鼠标指针偏移问题基本会消失。如果你已经建好了一个Windows虚拟机可以在QEMU命令行里挂载virtio-win的ISOqemu-system-x86_64 \ -drive file/path/to/virtio-win-0.1.229.iso,mediacdrom进入Windows后打开光驱直接运行virtio-win-guest-tools把所有组件安装一遍。6. 一套可复用的排错顺序与个人心得说完了各种方案最后聊一聊我在实践中总结的一套排错顺序。遇到传文件不成功我现在的检查路径非常固定。6.1 从下往上查链路、权限、协议我的排错逻辑是从物理层到协议层逐步排查。第一步先确认基本连通性。如果是网络传输先在虚拟机里ping一下网关、在外边ping一下虚拟机的映射IP如果是共享目录先确认挂载点存在、mount命令没报错。连通性都不通直接定位到网络配置或共享参数上。第二步看权限。虚拟机和宿主机用户权限不一致经常导致能挂载但不能写入。用security_modelnone能消除大部分坑但要注意安全边界。第三步看协议细节。比如9p的version9p2000.L、virtiofs的mount_tag是否匹配、ssh端口是否写对。细节问题要逐项核对。我专门整理过一个简表可以帮你快速定位症状可能原因解决方向虚拟机里mount 9p报Host requires a more recent Windows 9P server虚拟机内核旧换用version9p2000.L升级内核9p挂载成功但写入卡死缓存模式/cacheloose与文件使用场景冲突换cachenone观察检查多设备冲突virtiofs启动报shared memory相关错误QEMU内存没配成memory-backend-file检查启动参数中的-object memory-backend-fileSSH连接被拒绝虚拟机里sshd未启动检查虚拟机22端口检查hostfwd映射目标端口虚拟机能ping通外网但SSH连回来超时端口映射或防火墙问题检查hostfwd和宿主机防火墙qemu-nbd挂载时报错nbd模块未加载或镜像损坏modprobe nbd max_part8用qemu-img检查Windows虚拟机设备管理器一堆感叹号缺virtio驱动挂上virtio-win ISO安装6.2 我踩过的几个典型坑第一个坑是9p挂载加了multidevsremap还是报inode冲突。后来发现问题出在共享目录本身挂载了另一个设备而那个设备的文件系统和宿主机根分区不是同一个9p在遍历目录时看到不同设备的inode会混乱。解决的办法其实很简单不要在共享目录里跨设备塞东西要么就把共享目录拆成多个独立的9p挂载而不是用一层目录强行包住所有设备。第二个坑是virtiofs的共享内存配置。网上很多教程都省略了-object memory-backend-file我照抄之后虚拟机一直起不来日志提示vhost-user requires shared memory。后来仔细看文档才发现virtiofs依赖共享内存后端才能实现page cache共享。这个参数必须写不能省。第三个坑是rsync传大目录时因为虚拟机里磁盘性能一般跑到一半报rsync error: some files/attrs were not transferred。原因是在传输过程中虚拟机的文件被其他进程改了导致rsync的校验失败。解决方式是加上--ignore-errors或者先停掉写频繁的服务再重新跑。6.3 我的通用建议我现在的实践中日常开发用的最多的是virtiofs因为它性能好而且可以直接把宿主机上的代码目录挂进虚拟机改完代码虚拟机里马上能看到跑测试、编译都方便。临时传小文件我反而最常用的是用户模式网络加scp。在QEMU命令行里给端口转发配好然后一条scp就搞定不依赖9p/virtiofs的内核支持。大块数据或者要备份整个虚拟机我会优先考虑qemu-img convert加rsync配合先在宿主机上把qcow2转成raw或者打快照然后归档。这样不管是备份还是迁移都绕开了虚拟机的网络瓶颈。另外个人习惯上我会尽量让QEMU的共享目录是只读的。给开发同学用的共享目录写权限往往很容易造成两边文件错乱。如果是生产环境、数据敏感的虚拟机尽量别用security_modelnone否则虚拟机的root一旦被入侵宿主机等于门洞大开。这个安全边界一定要想清楚。最后一个很容易被忽略的小技巧如果是纯文本或配置类文件直接用串口传也是可行的。QEMU里给虚拟机加一个-serial stdio然后在虚拟机里把文件内容打到串口宿主机这边直接读终端输出。这个方法看起来原始但在虚拟机网络和共享目录全都不可用的时候往往是最后一根救命稻草。