前阵子帮同事配一台 Ubuntu 开发机,他的诉求很朴素:Windows 主机上有一堆资料和代码,想直接在 Ubuntu 虚拟机里读写,不想每次都插 U 盘、也不想来回复制粘贴。我到了才发现,他折腾了整整一个下午,/mnt/hgfs目录是空的,改了三遍fstab,重启之后虚拟机直接卡在启动阶段进不去系统。这类问题其实一点都不难,难的是没人把"为什么要这么做"讲清楚,导致每次遇到报错只能靠搜。这篇就把虚拟机中 Ubuntu 与主机共享文件夹这件事从头到尾捋一遍——VMware 和 VirtualBox 两条主流路线怎么走、走不通时怎么换成网络挂载、开机自动挂载为什么会失效、权限和编码这些反复咬人的细节怎么处理,以及哪些目录打死都不能放进共享文件夹。不管你是刚装好虚拟机的新手,还是已经被fstab坑过一次的老手,应该都能在这里找到自己缺的那一块。
1. 共享文件夹不是"拖拽"的替代品,先把这笔账算清楚
1.1 把文件送进虚拟机,一共有四条路
很多人一上手就想着配共享文件夹,但其实在虚拟化环境里搬运文件,至少有四种方式,各自适用的场景完全不同。
第一种是拖拽和剪贴板共享。装了open-vm-tools-desktop或者 VirtualBox 的增强功能包之后,主机和虚拟机之间可以互相拖文件、复制文本。这个方式胜在零配置,临时传个 PDF、复制一段命令非常方便。但它的短板也很明显:大文件慢得离谱,文件夹拖拽经常丢文件,而且没有稳定的路径——虚拟机里的程序没法引用"我刚拖进来的那个文件"。它适合人,不适合程序。
第二种是网络传输,也就是scp、sftp、rsync或者直接开个 Samba 服务。这种方式最通用,跨宿主机的方案都一样,网速也稳。缺点是每次都要敲命令或者开客户端,对于"我要持续在某个目录里写代码"这种高频场景,手感不连贯。
第三种是虚拟化平台自带的共享文件夹,VMware 叫 Shared Folders(底层是 HGFS 协议),VirtualBox 叫 Shared Folders(底层是 vboxsf 驱动)。配置好之后,主机的一个目录会以挂载点的形式出现在 Ubuntu 里,像本地目录一样cd进去就能用。这是最贴近"无缝"的方案,也是这篇要重点讲的。
第四种是版本库或者网盘。代码走 Git,资料走内网同步盘,本质上是用一个中间层解耦。多人协作、需要版本回溯的场景这才是正解,但单机开发时它就是绕远路。
1.2 共享文件夹真正的价值在哪
我个人的判断标准很简单:如果一个目录我一天要进出十次以上,就值得配共享文件夹;否则不如用scp。
举个具体的例子。我在 Ubuntu 里跑 Zephyr 的编译环境,但代码编辑器还是习惯用主机上的 VS Code。这种情况下共享文件夹几乎是唯一解——主机侧编辑、虚拟机侧编译,一套代码两个系统共用。反过来,如果只是偶尔从主机拷一个gcc安装包进去,那scp一句话就完事了,配共享文件夹纯属浪费时间。
还有一个容易被忽略的点:共享文件夹是双向的。你在 Ubuntu 里新建、删除、改名的文件,主机上同步可见。这意味着你可以把它当成一个跨系统的"中转站",而不是单向的导入通道。
不过这里要先打一剂预防针。共享文件夹的性能、权限模型、文件锁机制和本地磁盘都不一样,它不适合承载编译产物、数据库文件、依赖目录。这个结论后面第 8 章会展开讲,现在只要记住一句话:共享文件夹适合放"源码和文档",不适合放"构建产物和运行时数据"。
2. VMware 路线:open-vm-tools 到 /mnt/hgfs 的完整落地
2.1 宿主机侧:设置面板里的三个关键开关
VMware 的共享文件夹配置全在虚拟机设置里。关闭虚拟机(或者至少确认不是挂起状态),打开虚拟机设置 → 选项 → 共享文件夹,这里有三档:已禁用、始终启用、在下次关机或挂起前启用。
我一般直接选始终启用。选"下次关机前启用"的话,重开虚拟机之后共享就没了,很多人第一次踩坑就是踩在这里——明明昨天下班前还好好的,今天开机/mnt/hgfs就空了。
然后点"添加",填三个东西:
- 主机路径:主机上你要共享的目录,建议选一个路径里没有空格、没有中文的目录。中文路径在老版本的 vmhgfs 驱动上会出现挂载后乱码甚至挂载失败的情况。
- 名称:共享名,这是虚拟机里看到的标识符,建议用小写英文加下划线,比如
share_code。 - 启用此共享:勾上。"只读"按需勾,如果你的目的是在虚拟机里写文件,记得别勾只读。
注意:改完共享文件夹设置之后,如果虚拟机正在运行,guest 侧的挂载点不会自动刷新。要么重启虚拟机,要么手动重新挂载一次,别以为是配置没生效。
2.2 客户机侧:为什么装 open-vm-tools 而不是官方安装包
Ubuntu 上装 VMware Tools 有两条路:从 VMware 菜单里点"安装 VMware Tools"挂载虚拟光驱,然后手动解压、跑vmware-install.pl;或者直接apt装open-vm-tools。
我强烈建议走第二条,而且只用第二条。
sudo apt update sudo apt install -y open-vm-tools open-vm-tools-desktop理由有三个。第一,open-vm-tools是 Ubuntu 官方仓库维护的版本,和内核版本是配套的,内核升级之后不会出现驱动编译失败、模块加载不上的问题。第二,官方那个vmware-install.pl装出来的驱动是编译到内核的,每次apt upgrade换内核,你的共享文件夹就大概率挂掉,还得重新跑一遍安装脚本。第三,open-vm-tools-desktop这个包负责分辨率自适应、剪贴板共享、拖拽,少了它你会发现屏幕只有 800x600,而且剪贴板不通。
装完之后确认服务在跑:
systemctl status open-vm-tools正常应该显示active (running)。如果是inactive或者failed,先看一眼它为什么起不来,别急着去搞挂载——驱动层没起来,后面全是空谈。
2.3 手动挂载一次,把参数彻底摸清楚
工具装好了,先别急着写fstab。第一步是手动挂载成功,确认路径和参数都对,这一步能帮你排掉九成的后续问题。
先确认虚拟机能不能看到你在设置里加的那个共享:
vmware-hgfsclient这条命令会列出所有可用的共享文件夹名称。如果它输出了你配的那几个名字,说明驱动层是通的;如果什么都不输出,那问题在 VMware 配置或者 open-vm-tools,不在挂载。
然后创建挂载点并挂载:
sudo mkdir -p /mnt/hgfs/share_code sudo /usr/bin/vmhgfs-fuse .host:/share_code /mnt/hgfs/share_code \ -o subtype=vmhgfs-fuse,allow_other,uid=1000,gid=1000这里的参数值得逐个解释:
.host:/share_code是 HGFS 协议的特殊路径,.host:代表主机侧根目录,后面跟共享名。subtype=vmhgfs-fuse让内核知道这是 fuse 类型的 vmhgfs 文件系统。allow_other允许非 root 用户访问挂载点。不加这个参数,普通用户cd进去会得到Permission denied,这个坑极其常见。uid=1000,gid=1000把挂载点里的所有文件都映射成你的普通用户身份。你的 uid 用id -u确认一下,第一个普通用户通常是 1000。
想一次性挂载所有共享,把源写成.host:/就行:
sudo /usr/bin/vmhgfs-fuse .host:/ /mnt/hgfs -o subtype=vmhgfs-fuse,allow_other挂载完ls /mnt/hgfs/share_code,能看到主机上的文件就成了。
2.4 /mnt/hgfs 是空的,按这个顺序查
这是被问得最多的一个问题。排查顺序我总结成一条链,从上往下走,别跳步:
- 先跑
vmware-hgfsclient。没输出 → 回到虚拟机设置检查"共享文件夹是否启用"、共享是否勾选、虚拟机是否需要重启。 - 有输出但还是空→ 检查
/mnt/hgfs这个目录本身是否存在。很多时候是挂载点目录被删了,mkdir一下就好。 - 目录存在、挂载命令也没报错,但
ls是空的→ 大概率是allow_other没加,或者你用的是普通用户去访问 root 挂的东西。 - 报
fuse: device not found或类似错误→ fuse 相关组件缺失,sudo apt install fuse3补上。 - 挂载命令直接说找不到
vmhgfs-fuse→open-vm-tools没装好,回到 2.2 重新装。
最后再提醒一个细节:如果你曾经手动装过官方 VMware Tools,又装了open-vm-tools,两者会打架,/usr/bin/vmhgfs-fuse可能被覆盖成旧版本。这种情况先把手动装的卸载干净再重来,比反复调试快得多。
3. VirtualBox 路线:增强功能包与 vboxsf 组的权限账
3.1 Guest Additions 的两种装法
VirtualBox 的共享文件夹依赖Guest Additions,装法和 VMware 类似,但细节不同。
第一种是图形化:启动虚拟机,菜单栏设备 → 安装增强功能,它会把一个 ISO 挂进光驱,然后在 Ubuntu 里手动执行:
sudo mkdir -p /mnt/cdrom sudo mount /dev/cdrom /mnt/cdrom cd /mnt/cdrom sudo ./VBoxLinuxAdditions.run第二种是走仓库:
sudo apt install -y virtualbox-guest-utils virtualbox-guest-x11两种装法的取舍,我的经验是:内核版本越新,越建议走仓库。官方 ISO 里的VBoxLinuxAdditions.run会现场编译内核模块,遇到新内核(比如 Ubuntu 24.04 的 6.8)经常报编译错误,报错信息还很难读。仓库版本跟着系统更新走,省心得多。
装完之后一定要重启虚拟机。vboxsf 是内核模块,不重启加载不上,mount -t vboxsf会直接告诉你"unknown filesystem type"。
3.2 vboxsf 用户组:九成的权限问题都出在这
VirtualBox 共享文件夹的权限模型和 VMware 完全不同,它不是靠uid参数映射,而是靠用户组。
原理是这样:虚拟机里的共享挂载点属于vboxsf用户组,只有该组成员才有读写权限。所以你要做的第一件事是把自己加进去:
sudo usermod -aG vboxsf $USER关键点:加完组必须重新登录(注销再登录,或者重启)才生效。很多人加完组之后在当前终端里试半天还是Permission denied,就是因为组的变更不会影响已经打开的会话。想确认是否生效,开一个新终端跑groups,看到列表里有vboxsf才算成功。
挂载命令是这样的:
sudo mkdir -p /mnt/share_code sudo mount -t vboxsf share_code /mnt/share_code注意share_code是在 VirtualBox 设置里填的共享名称,不是主机上的路径。
3.3 /media/sf_xxx 自动挂载点的坑
VirtualBox 有一个很方便的设计:如果在共享文件夹设置里勾了"自动挂载",它会在/media/下自动创建一个以sf_开头的挂载点,比如sf_share_code。
这个自动挂载点有两个特点需要记住:
一是它默认归属 root:vboxsf,普通用户能不能访问完全取决于你在不在vboxsf组里。所以 3.2 那一步是绕不开的。
二是它不支持自定义挂载参数。你不能给它加uid、umask这些选项。如果你需要精细控制权限,比如让某个服务进程以特定身份读共享目录,那自动挂载点就不够用了,得自己在设置里取消自动挂载,改用/etc/fstab手动配置。
我的一般做法是:日常开发用自动挂载点,省事;需要跑服务或者对接 CI 的场景,手动配fstab。两种模式在同一个虚拟机里共存也没问题,只是别挂同一个共享名两次。
4. 走网络的路:把主机目录用 CIFS 挂进 Ubuntu
4.1 什么时候该放弃 hgfs 和 vboxsf
平台自带的共享文件夹虽然好用,但有几种情况你必须转向网络挂载:
- 主机是 Windows,虚拟化平台是 Hyper-V 或者干脆是另一台机器;
- 你需要让宿主机之外的第三台设备也访问这个目录;
- 共享文件夹的性能怎么调都不行,想换条路试试;
- 你要在 Docker 容器里访问这个目录,而容器对 fuse 挂载点的穿透有限。
这时候 SMB/CIFS 就是标准答案。它的本质是:Windows 主机把目录共享出来,Ubuntu 作为客户端用cifs文件系统挂上去。
4.2 Windows 侧的共享设置,别漏掉账号凭证
先澄清一个概念:共享文件夹的访问靠的是"共享权限 + NTFS 权限"两套,取交集。很多人只在"共享"标签页里加了 Everyone 可读写,结果还是被拒,原因就在 NTFS 权限那一层。
操作路径:
- 右键目标文件夹 → 属性 →共享→ 高级共享 → 勾选"共享此文件夹" → 权限里给对应账号读写。
- 切到安全标签页,确认你的账号(或者 Everyone)在这也有读写权限。
- 记下主机在局域网里的 IP:
ipconfig看 IPv4 地址。
然后强烈建议单独建一个本地账号专门用于共享,别用你的微软账号。原因很实际:微软账号登录 Windows 时,网络凭证的用户名往往要用MicrosoftAccount\你的邮箱这种格式,而在 Ubuntu 的cifs挂载参数里写这个格式容易出转义问题。用一个简单的本地账号(比如smbuser)会省掉大量麻烦。
注意:Windows 的"密码保护的共享"开关如果开着,任何访问都必须提供有效账号密码。家庭环境里如果只是自己用,可以关掉;但只要这台机器连在办公网,就别关。
4.3 cifs-utils 挂载命令的每个参数都在干什么
Ubuntu 侧先装工具:
sudo apt install -y cifs-utils然后手动挂一次:
sudo mkdir -p /mnt/win_share sudo mount -t cifs //192.168.1.10/share_code /mnt/win_share \ -o username=smbuser,password=你的密码,uid=1000,gid=1000,iocharset=utf8,vers=3.0参数逐个拆:
| 参数 | 作用 | 不写会怎样 |
|---|---|---|
username/password | 访问共享的凭证 | Windows 侧开了密码保护时直接拒绝 |
uid/gid | 把挂载文件映射成哪个本地用户 | 文件属主是 root,普通用户改不了 |
iocharset=utf8 | 指定字符集 | 中文文件名显示成问号或方块 |
vers=3.0 | 指定 SMB 协议版本 | 可能协商失败,报Host is down |
file_mode/dir_mode | 精细控制文件/目录权限位 | 默认权限可能过宽或过窄 |
nofail | 挂载失败不阻塞启动 | 见第 5 章,严重时进不去系统 |
_netdev | 声明依赖网络 | 开机时网络还没起来就去挂,必然失败 |
vers这个参数单独说一句。SMB 协议有 1.0、2.0、2.1、3.0、3.1.1 好几个版本,Windows 10/11 默认禁用了 SMB 1.0。如果你的 Ubuntu 客户端默认协商到了 1.0,就会直接被服务端拒绝,报的错还特别有迷惑性——mount error(112): Host is down,看着像网络不通,其实是协议版本对不上。遇到这个报错,第一反应就是把vers=3.0加上。
4.4 密码不要写在命令行里
上面那条命令有个明显的问题:密码是明文的,而且会进 shell 历史。
正确做法是写一个凭证文件:
sudo mkdir -p /etc/samba sudo tee /etc/samba/cred_win <<'EOF' username=smbuser password=你的密码 EOF sudo chmod 600 /etc/samba/cred_win sudo chown root:root /etc/samba/cred_win然后挂载命令简化成:
sudo mount -t cifs //192.168.1.10/share_code /mnt/win_share \ -o credentials=/etc/samba/cred_win,uid=1000,gid=1000,iocharset=utf8,vers=3.0权限位一定要是600。cifs-utils会检查凭证文件的权限,太宽松的话会直接拒绝使用并给出警告。这是有意为之的设计,别想着绕过。
4.5 "Windows 11 找不到网络路径"的几种真实原因
这个报错在热搜里出现的频率很高,实际原因就那几类:
第一类是权限和协议。Windows 11 家庭版默认关闭了"不安全的来宾登录",从 Linux 侧匿名访问会被拒。解决方向是给共享配置一个真实账号,而不是试图绕过认证。
第二类是主机防火墙。Windows 防火墙的"文件和打印机共享"入站规则如果没启用,Ubuntu 侧ping得通但mount一定失败。检查一下防火墙里的专用网络配置文件是否允许了共享。
第三类是网络发现。两台机器如果不在同一个网段,或者路由器做了 AP 隔离,那就压根连不上。虚拟机网络用 NAT 模式时,虚拟机在另一个虚拟网段里,直接访问主机 IP 也可能不通,这种情况要么改桥接模式,要么用主机的虚拟网卡地址。
第四类是浏览器缓存。Windows 里\\主机名\共享名打不开但\\192.168.1.10\共享名能打开,基本都是名称解析的问题。我的习惯是全程用 IP 地址,不做主机名解析,能省掉一大类玄学问题。
5. 开机自动挂载:fstab 写错一个参数,重启就白干
5.1 vmhgfs-fuse 的 fstab 写法
手动挂载成功之后,把它固化到/etc/fstab:
.host:/share_code /mnt/hgfs/share_code fuse.vmhgfs-fuse allow_other,defaults,uid=1000,gid=1000 0 0几个要点:
- 文件系统类型写
fuse.vmhgfs-fuse,不能只写vmhgfs-fuse,否则mount找不到对应的挂载助手。 - 挂载点目录必须先
mkdir好,fstab不会帮你创建。 allow_other必须保留,原因和手动挂载一样。
改完fstab之后,先别重启,用这条命令验证:
sudo mount -amount -a会尝试挂载所有fstab里没挂上的条目,有语法错误会当场报出来。这一步是保命操作,一定要养成习惯。
5.2 cifs 挂载"重启后失效"的真正原因
同一条cifs挂载命令,手动执行百试百灵,写进fstab重启就挂不上——这个现象背后其实是一个非常确定的时序问题。
系统启动的时候,挂载文件系统的动作由systemd在早期阶段完成,而那会儿网络栈往往还没初始化完。cifs挂载需要跟远端建立 TCP 连接,网络没起来自然就失败。更麻烦的是,如果fstab里没有声明容错,systemd会因为"本地文件系统挂载失败"进入紧急模式,你看到的就是一个黑底白字的emergency shell,连桌面都进不去。
这就是为什么_netdev和nofail这两个参数几乎是fstab里cifs条目的标配:
//192.168.1.10/share_code /mnt/win_share cifs credentials=/etc/samba/cred_win,uid=1000,gid=1000,iocharset=utf8,vers=3.0,_netdev,nofail 0 05.3 _netdev、nofail、x-systemd.automount 到底谁救谁
这三个选项经常被混着用,但它们的职责完全不同:
_netdev:告诉systemd"这个挂载依赖网络",于是它会把这个挂载排到网络就绪之后再执行,而不是并行启动。它解决的是时序问题。nofail:告诉systemd"这个挂载失败无所谓,别卡住启动"。它解决的是容错问题。x-systemd.automount:把挂载改成按需触发——只有你第一次访问挂载点目录时,系统才真的去建立连接。它同时能解决时序问题和启动阻塞问题,代价是第一次访问会有一点点延迟。
我的实际配置习惯是三者搭配:
//192.168.1.10/share_code /mnt/win_share cifs credentials=/etc/samba/cred_win,uid=1000,gid=1000,iocharset=utf8,vers=3.0,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=600 0 0x-systemd.idle-timeout=600的意思是空闲十分钟自动断开,下次访问再重连。对不常访问的共享来说,这样能少占用一点资源,也让网络波动时的恢复更顺滑。
注意:用了
x-systemd.automount之后,原来的挂载点目录会被 systemd 用一个 autofs 目录接管。这时候ls /mnt/win_share看起来是空的,别慌,cd进去就会触发真实挂载。
5.4 把虚拟机搞进不了系统之后怎么救
回到开头说的那个同事。他在fstab里写了cifs条目,没加nofail,重启之后进 emergency shell,而他又不知道 root 密码——因为 Ubuntu 默认锁了 root 账户。
救援路径是:
- 在 emergency shell 里输入 root 密码。如果没设过,先想办法进单用户模式。
- 更通用的办法是:在 GRUB 菜单里按
e编辑启动项,在linux那一行末尾加上systemd.unit=rescue.target或single,然后Ctrl+X启动,这样可以进到一个不挂载fstab里额外条目的救援环境。 - 进去之后把出问题的
fstab行注释掉,reboot。 - 最省事的办法其实是:用虚拟机快照。VMware 和 VirtualBox 都支持快照,改
fstab这种高风险操作之前打一个快照,出问题几十秒回滚。
这条经验我踩过一次就牢牢记住了:动/etc/fstab之前,先打快照。成本几乎为零,收益是省掉一整个下午。
6. 权限、编码、文件锁:三个反复咬人的细节
6.1 "权限不够"的三种不同含义
Permission denied这个错误在共享文件夹场景下至少对应三种不同的原因,得分开处理:
第一种是挂载参数层面的。VMware 路线下,如果你忘了allow_other,或者没写uid/gid,挂载点里的文件属主就是 root,普通用户读写全被拒。解决方式就是回到挂载命令,把参数补齐。
第二种是文件系统层面的。CIFS 挂载时,uid/gid只决定了"文件看起来属于谁",实际能不能写还取决于服务端给你的共享权限。也就是说,你在 Ubuntu 侧chmod 777是没用的——CIFS 的权限位是客户端映射出来的假象,真正说了算的是 Windows 那边的 NTFS 权限。这一点很多人理解反了,折腾半天chmod完全没效果。
第三种是换行符和可执行位。挂载点上的 shell 脚本,因为你没法在 CIFS 上真正设置可执行位(除非加file_mode=0775这类参数),./script.sh会报权限错误。解决办法是显式用bash script.sh调用,或者把脚本放到本地目录。
6.2 中文乱码和 iocharset
挂载之后文件名变成?????.txt,原因基本都是字符集没指定。
CIFS 挂载要加iocharset=utf8。较新的内核里这个参数可能被提示为已废弃,换用nls=utf8也可以,两个都写上去一般不会报错。
还有个更隐蔽的情况:文件名本身在 Windows 侧就是乱码的。Windows 用的是 UTF-16,而某些老的压缩包解压出来是 GBK 编码的文件名,这种在 Linux 侧怎么调参数都救不回来,得在 Windows 侧先重命名。
VMware 的 HGFS 和 VirtualBox 的 vboxsf 走的是自己的协议,基本不会有字符集问题。所以如果你对中文文件名有强需求,平台自带的共享文件夹会比 CIFS 更省心。
6.3 软链接、文件锁和 inotify:开发场景的三个暗雷
这三个是"配好了能用,但用着用着突然发现不对劲"的典型。
软链接:在共享文件夹里创建指向挂载点之外的符号链接,主机侧往往识别不了。CIFS 上默认软链接会被当成普通文件,需要加mfsymlinks参数。但即便加上,兼容性也只能说一般。我的建议是:项目里不要用跨边界的软链接,node_modules那种靠软链接组织的依赖目录尤其别放共享文件夹。
文件锁:数据库文件(SQLite、LevelDB)放在共享文件夹上,并发访问时锁机制会失效或者死锁,轻则报错重则数据损坏。这类文件必须放在虚拟机本地磁盘。
inotify:这是最容易被忽略、也最容易让人怀疑人生的一个。Webpack、Vite、nodemon这类工具靠 inotify 机制监听文件变化。而虚拟化平台的共享文件夹不会把主机的文件变更事件传递到 guest 侧,结果就是你在主机上改代码,Ubuntu 里的 dev server 毫无反应,手动重启才生效。
绕开的方式有几种:把源码放在虚拟机本地,主机通过 SSH 远程编辑;或者改用轮询模式(Vite 里是server.watch.usePolling: true),代价是 CPU 占用上升。我一般首选轮询,配置改一行就完事。
6.4 换行符:那个烦人的 ^M
Windows 的换行是CRLF,Linux 是LF。在共享文件夹里编辑的 shell 脚本,如果被 Windows 侧的编辑器改成了 CRLF,在 Ubuntu 里执行会报:
/bin/bash^M: bad interpreter: No such file or directory这个^M就是\r。解决方式有三种,按推荐度排序:
- 从源头解决:编辑器里设置该目录的换行符为 LF,VS Code 右下角就能切,顺手再配一个
.gitattributes(* text=auto eol=lf)。 - 事后修:
sed -i 's/\r$//' script.sh或者装dos2unix。 - 兜底:调用的时候用
bash script.sh,bash对\r的容忍度比内核加载 shebang 时要高一点,但这不是长久之计。
7. 反向打通:让主机和局域网直接访问虚拟机
7.1 网络模式选 NAT 还是桥接
共享文件夹是"主机到虚拟机"的方向,很多时候你还想要反方向:主机浏览器打开虚拟机里跑的服务,或者手机连虚拟机的测试接口。
这时候网络模式的选择就变得关键了:
| 模式 | 虚拟机的 IP | 主机能否直连 | 局域网设备能否直连 | 适用场景 |
|---|---|---|---|---|
| NAT | 虚拟网段,如 192.168.x.x | 需要端口转发 | 不能 | 只上网,不需要被访问 |
| 桥接 | 和主机同网段 | 能 | 能 | 需要被访问,推荐 |
| 仅主机 | 私有网段 | 能 | 不能 | 纯内网调试 |
我的默认选择是桥接。虚拟机拿到一个和主机同网段的 IP,主机和手机都能直接访问,不用配任何端口转发,省掉一堆心智负担。
注意:桥接模式下虚拟机会暴露在局域网里,装完系统记得看一眼
ufw的状态。默认 Ubuntu 桌面版的ufw是不启用的,服务端口是敞开的。
7.2 从主机 SSH 进虚拟机:四步搞定
这是使用频率最高的反向通道,配置也很简单:
sudo apt update sudo apt install -y openssh-server sudo systemctl enable --now ssh sudo systemctl status ssh四步之后,在虚拟机的终端里跑ip addr,找到inet那一行的地址,比如192.168.1.23。回到主机开一个终端:
ssh 你的用户名@192.168.1.23连不上的排查顺序是:先ping通不通(不通就是网络模式的问题),ping通但ssh连不上就看systemctl status ssh服务在不在跑,服务在跑就看sudo ufw status防火墙拦没拦。这三步能覆盖绝大部分情况。
用 SSH 还有一个额外好处:你可以把 VS Code 的 Remote-SSH 指向虚拟机,这样源码放在虚拟机本地磁盘(避开 inotify 和性能问题),编辑体验还是主机上那套,两全其美。这个组合是我目前最推荐的开发环境方案,比共享文件夹方案更稳。
7.3 主机浏览器打开虚拟机里的服务
在 Ubuntu 里跑了一个 web 服务,监听8080:
python3 -m http.server 8080如果网络是桥接模式,主机浏览器直接访问http://192.168.1.23:8080就能打开。
但有个坑要注意:服务监听的地址是127.0.0.1还是0.0.0.0。很多开发服务器默认只监听本地回环,这种情况下外部怎么都连不上。启动时显式绑定:
python3 -m http.server 8080 --bind 0.0.0.0Node 项目里常见的是在配置里改host: '0.0.0.0',Flask 里是app.run(host='0.0.0.0')。这几个地方的默认值都不一样,遇到连不上先往这里想。
7.4 局域网其他设备访问的注意事项
手机连虚拟机做移动端调试时,除了上面说的绑定地址,还要确认:
- 主机和手机在同一个 Wi-Fi 下,并且路由器没开客户端隔离;
- 虚拟机防火墙放行对应端口:
sudo ufw allow 8080/tcp; - 如果服务用了 HTTPS 自签证书,手机上要先信任证书,否则请求会被静默拦截。
8. 性能与工程习惯:哪些目录绝对不能放共享文件夹
8.1 共享文件夹的 IO 到底慢多少
先说结论:虚拟化平台的共享文件夹,IO 性能大约是本机磁盘的十分之一到二十分之一,尤其是小文件随机读写。
原因在于数据要经过一层协议转换——VMware 走 HGFS、VirtualBox 走 vboxsf,每次文件操作都要在 guest 和 host 之间来回传消息。单个大文件的顺序读写还好,但 npm 安装那种动辄几万个小文件的操作,性能差距会被放大到难以忍受的程度。
我做过一个不太严谨的对比:在一个中型前端项目里跑npm install,放在虚拟机本地磁盘大概 40 秒,放在共享文件夹里跑了六分多钟,而且经常在中途因为文件锁冲突中断。
8.2 黑名单:这几类目录请远离共享文件夹
| 目录类型 | 为什么不能放 | 替代方案 |
|---|---|---|
node_modules、venv、target | 海量小文件,IO 灾难;软链接兼容性差 | 放虚拟机本地磁盘 |
.git仓库对象 | 频繁随机读写,且涉及文件锁 | 放本地,或者只用只读拷贝 |
| SQLite / LevelDB 等嵌入式数据库 | 文件锁在共享文件系统上不可靠,有损坏风险 | 绝对放本地 |
| 编译中间产物 | 大量临时文件创建删除 | 放本地,或指向/tmp |
| Docker 数据目录 | 依赖 overlayfs 和特定内核特性 | 放本地 |
8.3 我实际用的目录划分方案
踩了几轮坑之后,我现在的目录结构是这样分的,供参考:
~/work/ # 虚拟机本地磁盘,放代码仓库、依赖、构建产物 /mnt/hgfs/share/ # 共享文件夹,放设计稿、需求文档、测试数据、安装包 ~/downloads/ # 虚拟机本地,从共享目录拷进来的东西先落在这里具体分工的逻辑是:
- 代码本身放本地。因为 inotify、文件锁、性能这三件事,代码放共享文件夹全是减分项。
- 资料和文档放共享。这类文件访问频率低、体积小、只需要"能打开",性能完全不敏感。
- 大文件先拷后跑。比如一个 2GB 的数据集,先
cp到本地磁盘再跑训练或者分析,比直接在共享目录上处理快得多。
同步代码用 Git,同步资料用共享文件夹,这个组合用了大半年没出过问题。
8.4 一个容易被忽略的收尾习惯
最后分享一个实际操作里的小习惯:每次改完挂载配置,把当前可用的配置命令记到虚拟机里一个固定的文本文件里,比如~/notes/mount-setup.md。
理由很实际:虚拟化平台升级、内核升级、系统重装都会让共享文件夹失效,而重新排查一遍的成本远高于查一次笔记。我在 Ubuntu 系统重装之后重建环境的次数不下五次,有这份笔记每次十分钟就能恢复,没有的话就得重新翻一遍这篇文档里讲的所有内容。
同理,open-vm-tools和cifs-utils这类包的安装命令也一并记上。这些东西平时记不住,但需要的时候又特别急,提前存一份比什么都强。
另外提一句硬件资源:装 Ubuntu 虚拟机的时候,内存别给太少,给到 8GB 以上、CPU 给 4 核,能省掉很多"以为是共享文件夹的问题、其实是虚拟机卡"的误判。虚拟机的资源分配不到位,再好的挂载配置也跑不起来。