首页 / 资讯中心 / 文章详情

QEMU虚拟机与宿主机文件共享:virtiofs、9p与网络传输全攻略

QEMU虚拟机与宿主机文件共享:virtiofs、9p与网络传输全攻略 ★ FEATURED ARTICLE
在QEMU虚拟机和宿主机之间传输文件这件事说难不难说简单也确实有点绕。很多人是从VMware或VirtualBox转过来的习惯了“共享文件夹”“拖拽复制”这种开箱即用的功能一到QEMU这儿就发现命令行启动的虚拟机居然连个图形化的文件共享入口都没有顿时就懵了。我第一次用QEMU的时候也踩过这个坑。当时用-net user起了个arm64的虚拟机系统能跑起来但想把一个编译好的二进制塞进去竟然找不到一个合理的方法。试过在宿主机上起HTTP服务用wget去拉能行但总觉得太“野路子”后来试了9p结果权限、挂载参数折腾了半天。直到我把virtiofs、9p、网络传输、磁盘镜像这几条路全部走了一遍才算彻底搞明白QEMU的文件传输到底该怎么做。这篇文章我就把QEMU宿主机和虚拟机之间传文件的几种主流方案、底层原理、实操命令、还有我踩过的那些坑一次性讲清楚。不管你是跑x86还是模拟arm64是Linux虚拟机还是Windows虚拟机都能在里面找到适合你的方案。1. 方案选型在开始传文件之前先想清楚这三件事很多人一上来就问“QEMU怎么共享文件夹”但实际操作下来选什么传输方案取决于另外三个问题你的虚拟机和宿主机之间是什么网络模式虚拟机的操作系统是什么你要传的是大文件还是小文件、是偶尔传一次还是频繁双向同步这三个问题的答案直接决定你用下面哪一种方案。为了让你在后续阅读时不迷路先把我用过的几条路线做个整体对比传输方案适用场景传输方向性能表现配置复杂度virtiofs 共享目录Linux虚拟机需要高性能双向共享双向实时共享接近原生磁盘性能中9p (virtio-9p) 共享目录Linux虚拟机轻量级共享双向实时共享一般小文件尚可低网络传输SSH/SFTP/HTTP所有系统尤其是Windows虚拟机双向但依赖网络取决于网络和虚拟网卡配置低磁盘镜像挂载qemu-nbd/libguestfs虚拟机已关机时批量读写单向/双向离线取决于挂载方式中ISO/虚拟光驱传文件一次性、只读传输仅宿主机到虚拟机安装介质级别速度低如果你只是想在宿主机和Linux虚拟机之间快速交换几个配置文件那我建议直接用网络方案省事如果你需要在虚拟机和宿主机之间做开发编译代码目录共享那种那virtiofs是正解如果是Windows虚拟机那就老老实实走网络共享或者Samba。我自己最常用的组合是日常小文件走SSH/SFTP开发环境走virtiofs关机后的镜像批量操作走qemu-nbd挂载。这三个方案覆盖了我90%以上的使用场景。下面我逐个详细拆解每个方案都给出实测可用的命令和配置。2. virtiofs共享目录性能最强的实时共享方案2.1 virtiofs的原理和选型理由virtiofs是QEMU/KVM虚拟化环境下专门为文件共享设计的一套virtio设备它和老的9p方案最大的区别在于9p是网络文件系统协议的虚拟化适配而virtiofs是专门为虚拟化场景从头设计的高性能共享文件系统它利用了内核的FUSEFilesystem in Userspace机制加上DAX直接访问支持理论上能达到接近原生磁盘的读写性能。我实测的感觉是在编译大型项目时virtiofs比9p明显快一个档次特别是大量小文件读写的场景virtiofs几乎感觉不到“这是一块网络盘”而9p在同样的场景下会出现明显的延迟。2.2 宿主机侧启动QEMU时添加virtiofs配置用virtiofs首先在宿主机上启动QEMU的命令里要加上相应的参数。下面是我跑arm64 Ubuntu虚拟机时实际用过的启动参数片段qemu-system-aarch64 \ -machine virt \ -cpu cortex-a72 \ -m 4096 \ -smp 4 \ -drive fileubuntu.img,formatraw,ifvirtio \ -device virtio-net-pci,netdevnet0 \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -chardev socket,idchar0,path/tmp/virtiofs.sock \ -device vhost-user-fs-pci,queue-size1024,chardevchar0,tagmyfs \ -object memory-backend-file,idmem,size4096M,mem-path/dev/shm,shareon \ -numa node,memdevmem这里有几个关键参数要特别注意-chardev socket,idchar0,path/tmp/virtiofs.sock创建一个socket chardev用于宿主机和虚拟机之间的virtiofs通信。-device vhost-user-fs-pci,queue-size1024,chardevchar0,tagmyfs这才是virtiofs设备本体。tag是挂载标签后面虚拟机里挂载时会用到可以自己取名字。-object memory-backend-file,idmem,size4096M,mem-path/dev/shm,shareon这是virtiofs能跑起来的关键中的关键必须配共享内存后端而且size要和-m指定的内存大小一致shareon表示这块内存是共享的允许传给vhost-user进程。我当初第一次配置virtiofs时漏了-object memory-backend-file和-numa node,memdevmem这两行结果虚拟机启动后根本看不到virtiofs设备日志里报vhost_user_fs: Failed to start vhost-user backend。这个坑我必须先给你标出来。2.3 虚拟机侧挂载virtiofs目录虚拟机的内核需要支持virtiofs现代发行版内核版本4.20以上基本都涵盖了默认开启。启动虚拟机后在虚拟机内部执行sudo mkdir -p /mnt/shared sudo mount -t virtiofs myfs /mnt/shared这里的myfs就是宿主机侧启动参数里tagmyfs设置的标签两边必须一致。挂载成功后/mnt/shared就是宿主机和虚拟机之间的共享目录了。注意virtiofs默认的映射方式是把宿主机根目录或指定目录“暴露”给虚拟机具体暴露哪个目录由宿主机端的vhost-user-fs后端程序决定——通常你需要在宿主机上先运行一个virtiofsd守护进程指定要共享的目录。比如宿主机上执行sudo virtiofsd --socket-path/tmp/virtiofs.sock --shared-dir/home/user/shared -o cacheauto--shared-dir就是你要共享给虚拟机的真实目录。如果你没启动virtiofsd虚拟机里挂载virtiofs肯定会失败这是很多人弄了半天没反应的原因。我建议把virtiofsd的启动命令和QEMU的启动命令一起写进脚本里避免忘记。2.4 virtiofs的权限和缓存问题权限virtiofs默认把宿主机上的文件权限直接映射到虚拟机里如果你在宿主机上用普通用户创建的文件在虚拟机里看到的是同一个UID/GID。如果两边用户ID不一致可能会遇到权限不够的问题。最简单的办法在宿主机和虚拟机上使用相同的用户名和UID。缓存-o cacheauto模式下virtiofs会积极缓存文件数据写入操作会先落内存后面再刷盘。如果你在宿主机侧修改了共享文件虚拟机里可能不会立即看到变化。遇到这种“文件明明改了虚拟机里却看不到”的情况要么在宿主机侧执行sync要么在挂载时直接-o cachenone禁用缓存代价是性能会有一定下降。提示如果你是在QEMU里跑arm64虚拟机而宿主机是x86_64virtiofs的CPU架构差异不影响挂载文件内容本身没有架构限制放心用。3. 9p共享目录轻量灵活小文件传输够用3.1 9p方案的设计思路9p是Plan 9操作系统遗留下来的网络文件系统协议QEMU通过virtio-9p设备把它带到了虚拟化场景。和virtiofs相比9p配置更简单不需要额外的vhost-user后端进程QEMU自己就把文件系统共享这件事干了。9p适合的场景是轻度使用、偶尔传几个配置文件、不想维护额外守护进程。如果你上了生产环境或者频繁做大量小文件读写9p性能确实有点捉急建议直接上virtiofs。3.2 宿主机侧一行参数搞定共享9p的启动参数比virtiofs简单得多不需要额外的socket、不需要共享内存配置、不需要单独启动守护进程。下面是实测可用的最小启动参数qemu-system-x86_64 \ -m 2048 \ -drive fileubuntu.qcow2,formatqcow2,ifvirtio \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device virtio-net-pci,netdevnet0 \ -virtfs local,path/home/user/shared,mount_taghostshare,security_modelpassthrough,idshare0参数解释local表示使用本地目录来提供共享这是最常见的模式。path/home/user/shared宿主机上要共享的目录建议用绝对路径。mount_taghostshare这个tag就是虚拟机里挂载时使用的标签。security_modelpassthrough这个参数决定了权限处理方式后面细说。idshare0设备ID可以随便取。3.3 虚拟机侧挂载与权限细节虚拟机启动后在虚拟机内执行sudo mkdir -p /mnt/host sudo mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/host这里和virtiofs有很大的区别9p的mount_tag是hostshare而不是宿主机目录名。很多新手直接把path/home/user/shared里的路径名当tag用结果挂载时报No such file or directory。security_model这个参数是9p最容易踩坑的地方三种常见取值的行为区别很大passthrough文件在虚拟机里表现为宿主机上的实际UID/GID权限以宿主机为准。这种方式最透明但会出现虚拟机的root在共享目录里“权限不够”的现象因为虚拟机的root看到的文件owner可能是宿主机上的普通用户。mapped-xattr将虚拟机UID/GID映射存储为文件扩展属性让每个虚拟机用户都有独立权限但需要文件系统支持user_xattr挂载选项。none不做权限映射虚拟机所有用户对共享目录都有完全控制权限安全性差一点但最省事。我在实际使用中如果是单用户虚拟机直接用security_modelpassthrough就够了如果需要多用户隔离建议mapped-xattr不过要注意共享目录所在文件系统必须支持扩展属性ext4、xfs都支持。注意用9p挂载共享目录后在虚拟机里写文件时如果宿主机的目录权限不足会报Permission denied这时候不是虚拟机里加sudo就能解决的要回到宿主机上调整目录权限。3.4 9p方案的一个小坑QEMU用户态网络下的限制如果你是用-netdev user的QEMU用户态网络栈同时挂载了9p有时候会碰到虚拟机网络和挂载同时异常的情况。别怀疑是9p的问题多数时候是QEMU内部对user网络和virtio-9p同时使用的兼容性bug解决方法是把网络换成-netdev tap或-netdev bridge或者顺序调整一下设备的加载顺序。我遇到过一次把-virtfs参数调整到-netdev参数之前问题就消失了原因说不清但实测有效。4. 网络传输最通用的文件交换方式4.1 为什么网络传输是最通用方案如果虚拟机和宿主机之间的网络已经通了服务器领域直接传文件才是最省心的方式毕竟它不依赖任何共享文件系统不受9p/virtiofs那些权限模型的限制Windows虚拟机、macOS虚拟机、BSD虚拟机都能用。QEMU默认的-netdev user模式已经内置了一个TFTP服务器和SMB服务器但说实话那两个都难用。我推荐的是宿主机上有SSH服务虚拟机里用scp/sftp拉取或者反过来虚拟机里开SSH服务宿主机直接连进去传文件。4.2 方案一虚拟机访问宿主机文件这种场景更常见。宿主机上有一些文件要传到虚拟机里你可以在宿主机上起一个临时的HTTP服务虚拟机里用wget或者curl去下载。宿主机上在要分享的目录执行cd /home/user/share_files python3 -m http.server 8000虚拟机里面执行wget http://10.0.2.2:8000/yourfile.tar.gz注意10.0.2.2这个地址这是QEMU用户态网络栈里给宿主机保留的网关地址。很多人第一次用QEMU用户网络时都好奇虚拟机里怎么访问宿主机答案就是10.0.2.2。这个方法本质上不需要启动SSH也不需要配置Samba起一个HTTP服务就完事了特别适合一次性传几个文件传完就关掉服务。4.3 方案二宿主机访问虚拟机文件反过来你想把虚拟机里的东西拷回宿主机最优雅的方式是在虚拟机里安装并启动SSH服务然后用hostfwd把22端口映射到宿主机的一个本地端口。虚拟机里执行sudo apt install openssh-server sudo systemctl enable --now ssh启动QEMU时已经加过端口转发参数的话-netdev user,idnet0,hostfwdtcp::2222-:22然后宿主机就可以用scp/sftp连接了scp -P 2222 userlocalhost:/home/user/vm_file.txt . sftp -P 2222 userlocalhost用-P 2222而不是默认端口是因为我们把虚拟机的22端口映射到了宿主机的2222端口。注意scp的-P是大写小写-p是保留文件属性的意思别弄混了。提示如果你用的是Windows宿主机传文件到Linux虚拟机可以用WinSCP或FileZilla连接localhost:2222本质上走的是同一个SSH协议图形化界面比命令行更方便。4.4 网络方案进阶端口转发的局限与替代hostfwd这个方案有一个局限性它只能在宿主机上监听固定端口再转发到虚拟机内部的服务。如果你想从另一台机器直接访问虚拟机里的服务就没那么直接了需要再加一层转发。更强大的网络方案是用tap设备加网桥把虚拟机直接放到宿主机所在的局域网里虚拟机获得一个局域网内的真实IP这样传文件就直接走局域网了带宽和延迟都比hostfwd好。不过tap网络配置稍微复杂一点需要root权限还要配置网桥我在这里不过多展开如果你只是偶尔传文件hostfwd完全够用。5. 离线传输方案用磁盘镜像挂载和ISO交换文件5.1 qemu-nbd挂载虚拟机磁盘镜像有时候虚拟机已经关机了你想直接从它的磁盘镜像里读取或修改文件比如虚拟机里的系统崩溃了需要紧急备份数据这时候网络和共享目录都用不了最好的办法就是用qemu-nbd把磁盘镜像挂载到宿主机上。qemu-nbd是QEMU自带的网络块设备工具可以把QEMU支持的各种磁盘镜像qcow2、raw暴露为Linux内核的nbd设备然后直接像普通硬盘一样mount。宿主机上需要先加载nbd内核模块sudo modprobe nbd max_part8然后挂载镜像sudo qemu-nbd -c /dev/nbd0 /path/to/ubuntu.qcow2 sudo partprobe /dev/nbd0partprobe会重新读取分区表让内核识别nbd0设备上的分区。如果你不确定虚拟机里分区结构可以先看一下lsblk /dev/nbd0 sudo fdisk -l /dev/nbd0找到根分区通常是/dev/nbd0p1或/dev/nbd0p2然后挂载sudo mount /dev/nbd0p1 /mnt/vm_disk挂载完成后就能直接读写虚拟机里的文件了。操作完成后一定要记得按顺序卸载sudo umount /mnt/vm_disk sudo qemu-nbd -d /dev/nbd05.2 用ISO镜像传文件这个方法比较复古但胜在通用性极强。把文件打包成ISO镜像挂载为虚拟机的光驱虚拟机里就能看到这些文件。对Windows虚拟机尤其好用因为Windows虚拟机没有现成的virtiofs支持网络配置又比较麻烦。宿主机上把文件打包成ISOsudo apt install genisoimage genisoimage -o transfer.iso /path/to/files/启动QEMU时挂载ISO-drive filetransfer.iso,mediacdrom,ifvirtio如果虚拟机已经在运行了可以在QEMU monitor里动态挂载(qemu) change device eject cd0 (qemu) change device add transfer.iso虚拟机里就能看到光驱里的文件了。这个方法适合传比较大的文件包而且因为是只读介质虚拟机里的操作不会影响原文件不用担心误改。注意用genisoimage打包ISO时如果文件名包含中文或特殊字符建议加上-J -R参数生成Joliet和Rock Ridge扩展记录否则Windows或Linux虚拟机里可能显示乱码。5.3 libguestfs工具集更安全的离线镜像操作qemu-nbd直接挂载镜像有一个风险如果操作不当容易破坏虚拟机文件系统元数据。如果你对文件系统操作不够熟练更推荐用libguestfs工具集它是在用户态完成镜像操作的不需要加载内核模块也不会直接暴露块设备安全性高很多。安装libguestfs-tools后你可以用guestfish或virt-copy-in/virt-copy-out来传文件# 把宿主机文件复制进虚拟机镜像 virt-copy-in -a ubuntu.qcow2 /home/user/hostfile.txt / # 把虚拟机镜像内文件复制出来 virt-copy-out -a ubuntu.qcow2 /etc/passwd /home/user/libguestfs底层用的是SELinux策略里放行的qemu进程完成的属于官方推荐的镜像离线修改方案比手动挂载nbd更不容易把镜像搞坏。我第一次用qemu-nbd直接挂载qcow2做实验时因为虚拟机那个分区是LVM的差点把数据弄丢后来换成libguestfs才安心。6. 常见问题与排查技巧实录6.1 9p挂载报错No such file or directory这个错特别误导人它不一定是你路径写错了绝大多数情况是mount_tag对不上。注意QEMU启动参数里mount_taghostshare虚拟机挂载时mount -t 9p -o transvirtio,version9p2000.L hostshare /mnt/hosthostshare这里必须是启动参数里mount_tag定义的值而不是宿主机物理路径。如果你已经检查过tag没问题再确认一下虚拟机内核是否支持9pgrep 9p /proc/filesystems没有输出的话就说明内核没编译9p支持需要换内核或者使用其他方案。6.2 virtiofs挂载后虚拟机里没反应virtiofs的排查顺序很重要先确认宿主机上virtiofsd进程是否在运行ps aux | grep virtiofsd没运行就启动。确认QEMU启动参数里-object memory-backend-file的size是否和-m大小一致不一致会导致共享内存分配失败。确认/dev/shm是否有足够空间内存大小别超过/dev/shm的容量。确认虚拟机内核支持virtiofsgrep virtiofs /proc/filesystems。最后才是检查挂载参数里的tag是否一致。顺序不能反我遇到过几次都是virtiofsd忘了启动导致虚拟机侧挂载时报transport endpoint not connected这个报错很容易让人误以为网络问题其实是后端进程没起来。6.3 QEMU用户态网络下虚拟机ping不通宿主机这个问题不算文件传输专属但会连带影响网络传输方案。QEMU的user网络模式下虚拟机里访问宿主机要用10.0.2.2不是192.168.x.x。如果你在虚拟机里ping 10.0.2.2都ping不通检查一下宿主机的防火墙是否拦截了来自TAP/TUN设备的流量有些发行版默认防火墙会拦。另外提醒一下QEMU的user模式网络默认不支持ICMP也就是ping不通宿主机网关但TCP/UDP是通的所以如果ping失败但wget/scp正常别慌这是正常的。6.4 Windows虚拟机的文件传输方案推荐Windows虚拟机的情况比较特殊它不像Linux那样自带9p/virtiofs的内核支持。我用过的几种方法按推荐程度排序优先级方案说明首选Samba共享网络磁盘在Windows里通过\\10.0.2.2\share访问Linux宿主机的Samba共享需要宿主机装Samba次选SSH/SFTPWindows里用WinSCP连宿主机SSH端口从宿主机拉取文件备选ISO光驱冷门但稳定适合一次性传大量文件不推荐virtiofs需要额外安装Windows的virtiofs驱动折腾成本高如果你在宿主机上配置了SambaWindows虚拟机里访问路径是\\10.0.2.2\sharename这个地址同样走QEMU的user网络网关。我第一次用的时候搞了半天一直用宿主机的局域网IP去访问结果访不到后来想起user模式下就应该用10.0.2.2一下就通了。6.5 文件传输后的权限问题经常有人遇到这种情况用网盘方案或者共享目录往虚拟机里传了文件但虚拟机的应用程序打不开提示没有权限。根本原因是UID/GID映射不一致。9p的passthrough模式下宿主机文件owner的UID直接映射到虚拟机。比如宿主机文件是UID1000的用户创建的而虚拟机里登录用户UID是1000但两者其实是不同用户账号名可能一样也可能不一样。这种情况最简单的解决办法是在宿主机上用chown把文件owner改成999或者其他数字然后在虚拟机里也用chown改成对应UID。或者更省事一点用security_modelnone挂载虚拟机里所有用户都能访问不过安全性会降低。6.6 传输大文件时性能很慢如果你的方案是网络传输传大文件很慢多数原因是QEMUuser网络模式的虚拟网卡性能有限还有可能宿主机和虚拟机之间走的是半虚拟化网卡virtio-net但CPU支持有问题。几个提速方向启动参数加-device virtio-net-pci而不是-net nic半虚拟化网卡比模拟e1000快很多。宿主机和虚拟机都确认网卡多队列multi-queue已开启QEMU参数加-smp 4和网卡的mqon。网络传输方案里优先用scp而不是FTPscp能吃到CPU的加密加速指令。如果传的是超大文件如几十GB的数据库备份建议直接用qemu-nbd挂载镜像的离线方案速度比网络快得多。7. 综合推荐不同场景下我用什么方案到这里几条路都讲完了。最后分享一套我自己在实际工作中的决策逻辑你可以直接照着选。场景开发调试代码在宿主机虚拟机里编译运行用virtiofs。共享目录挂载到虚拟机的开发目录宿主机改代码虚拟机立刻能看到。性能足够不需要来回拷贝。场景临时传几个安装包或配置文件用HTTP服务加wget。宿主机起一个python3 -m http.server虚拟机里直接下载传完即关零配置。或者反过来用scp一条命令搞定。场景虚拟机里的数据要备份到宿主机如果虚拟机在运行用scp/sftp从宿主机连接虚拟机的SSH服务把需要备份的文件拉出来。如果虚拟机已经关机或者系统坏了用libguestfs直接操作镜像。场景Windows虚拟机传文件宿主机配Samba共享或者直接用WinSCP连宿主机SSH。别折腾virtiofs和9pWindows驱动问题会让你怀疑人生。场景大批量文件虚拟机处于关机状态libguestfs工具集virt-copy-in和virt-copy-out安全可靠不用关心文件系统格式。我个人在实际使用中最深刻的体会是QEMU传文件没有“万能药”不同的场景选择不同的方案才能真正享受QEMU的灵活性。不要试图找一个像VMware共享文件夹那样一键搞定的东西QEMU是模块化的每一项能力都是独立组件但也正因为如此它在生产环境和嵌入式开发中的可定制性远超那些图形化虚拟机。最后再分享一个小技巧QEMU的-virtfs local参数可以多次添加一次启动挂载多个共享目录。如果你有多个目录要共享不用折腾什么新的工具把所有-virtfs参数都写到QEMU启动脚本里每个目录对应一个不同的mount_tag虚拟机里分别挂载到不同位置就好了。这样一个QEMU实例就能同时支撑开发目录共享、数据目录共享、配置目录共享互不干扰。
阅读完成 · 觉得有帮助?
咨询建站