虽然 Windows 已经成为了很棒的 Linux 发行版,不过在使用时会逐渐累积一个很大的 vhdx 文件。尽管我们可以迁移子系统文件到其他硬盘,但是总而言之都不是很方便使用。因此我希望将 wsl 的数据移动到移动硬盘中。
Tldr
如果对踩坑的过程不感兴趣,请直接跳到正确的挂载方式这一节。
前言
移动哪些数据到外置硬盘中呢?一种思路是将整个虚拟机都放进移动硬盘。由于 wsl 本质上是一个 vhdx,因此我们可以将这个 vhdx 放入移动硬盘,在其他电脑上使用时配置注册表。但以我在移动硬盘中使用虚拟机的经验,如果突然断连大概率出现数据损坏,而且我还希望可以同时在 Linux 和 Windows 上挂载,因此我决定将 /home/user
目录,也就是用户主目录移动到外置硬盘中。
最终,我的目标为:挂载移动硬盘到 wsl,用 btrfs 格式化,挂载为 /home/user
,在使用时将硬盘挂载到 /home/user
目录,作为 user 用户使用硬盘中的数据。恰好我手上正好有一块三星 970 Evo Plus 2TB 的版本,NVMe 1.3 的速度虽然不适合作为主硬盘,但是作为外置硬盘走雷电 4 或 USB4 跑满 10Gbps 或 20Gbps 的硬盘盒还是足够的。
初始步骤
新版的 wsl2 已经支持挂载硬盘,参考 https://learn.microsoft.com/en-us/windows/wsl/wsl2-mount-disk 中的介绍逐步执行命令:
首先看电脑中的磁盘:
PS C:\Users\user> GET-CimInstance -query "SELECT * from Win32_DiskDrive"
这里会显示连接到电脑上的设备 ID、标题、分区数量与模型:
DeviceID Caption Partitions Size Model
-------- ------- ---------- ---- -----
\\.\PHYSICALDRIVE1 ZHITAI TiPlus7100 2TB 3 2048407280640 ZHITAI TiPlus7100 2TB
\\.\PHYSICALDRIVE0 ZHITAI TiPlus7100 2TB 1 2048407280640 ZHITAI TiPlus7100 2TB
\\.\PHYSICALDRIVE2 Realtek RTL9210 NVME SCSI Disk Device 0 2000396321280 Realtek RTL9210 NVME SCSI Disk Device
我的电脑上连接了两块至态 TiPlus7100 2TB 的硬盘,外置硬盘则通过 ITGZ RTL9210 硬盘盒连接(三星、至态、ITGZ 请给我打广告费.jpg)。这里我要挂载 \\.\PHYSICALDRIVE2
,执行:
wsl --mount \\.\PHYSICALDRIVE2 --bare
这样只会添加硬盘但不会挂载:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 388.4M 1 disk
sdb 8:16 0 4G 0 disk [SWAP]
sdc 8:32 0 1T 0 disk /mnt/wslg/distro
/
sdd 8:48 0 1.8T 0 disk
没有挂载点的 /dev/sdd
就是目标。
格式化并挂载空硬盘
接下来首先格式化硬盘为 brtfs。在 Debian 下需要先安装 btrfs-progs
:
sudo apt install btrfs-progs
强制格式化:
sudo mkfs.btrfs -f /dev/sdd
创建目录并挂载:
mkdir -p /home/user
mount /dev/sdd /home/user
接下来创建用户并将其设置指定到 /home/user
sudo useradd -m -d /home/user -s /bin/bash user
sudo passwd user
sudo usermod -aG sudo user
切换到用户 user
并验证其 sudo
权限:
su - user
sudo whoami
设置 home 目录权限:
sudo chown -R user:user /home/user
切换用户:
su - user
wsl 设置主用户
[boot]
systemd=true
[user]
default=user
如果要从 WSL 2 卸载和分离磁盘,运行:
wsl --unmount <DiskPath>
挂载带内容的硬盘
在之后的使用中,我只需要执行以下步骤:
# 查看硬盘 ID
GET-CimInstance -query "SELECT * from Win32_DiskDrive"
# 挂载硬盘
sudo wsl --mount \\.\PHYSICALDRIVE2 --bare
# 查看 wsl 中的硬盘设备 ID
wsl -d Debian -u root -e lsblk
# 挂载硬盘为 home 目录
wsl -d Debian -u root -e mount /dev/sdd /home/user
奇怪的问题
最开始的时候一切都很美好,但是没过多久就开始出现问题。在使用 VSCode 时,必须在 /home/user
目录下以相对地址的形式打开文件夹才能正确打开 VSCode,否则会触发问题:
/mnt/c/Users/user/AppData/Local/Programs/Microsoft VS Code/Code.exe: Exec format error
实际上不只是 VSCode 用不了,所有 Windows 上的程序例如 cmd、explorer 等程序都无法使用。
在网上有许多关于此问题的讨论,大部分解答都是说重启 wsl 即可,但是在我这里始终无法解决问题。
除此之外,我还观察到一个现象:注意到 wsl --mount
命令需要管理员权限,我可以选择通过 sudo
命令运行,也可以以管理员权限启动终端后直接执行 wsl --mount
。
如果我在普通权限启动终端并执行上述命令,就会发生以上问题,且在文件管理器中无法查看到挂载后的磁盘内容。
如果我以管理员权限启动终端并执行上述命令,尽管上述问题不会发生,但我无法在普通权限终端开启的 wsl 中查看挂载的硬盘中的文件。
更有趣的是,这同时会导致 Docker Desktop 出问题,因此我选择卸载 Docker Desktop,直接在 wsl 中安装 docker,但这也有问题:在运行 docker 时指定的 volume 在移动硬盘中的话,docker 中仍然无法访问到移动硬盘中的文件。不得不说,这实在是令人心态爆炸。
正确的挂载方式
在研究了官方文档后,我认为问题在于不能在 wsl2 里面 mount,而是在外面:
wsl --mount \\.\PHYSICALDRIVE2 --type btrfs --name user
此时会挂载到 /mnt/wsl/user
,如果之前 home
为 /home/user
的话需要修改 home 目录:
sudo usermod -d /mnt/wsl/user user
可以检查 home
是否设置正确:
grep user /etc/passwd
可惜 wsl 的 mount 参数不支持挂载到指定地址,只能通过 automount 的 root
参数设置所有的 mount 地址。如果我设置该参数为 /home
,自动挂载的 C、D 盘也会被挂载到 /home
目录。因此我决定先保持默认设置,以后遇到问题再考虑解决。
--mount <Disk>
在所有 WSL 2 分发版中附加和装载物理磁盘或虚拟磁盘。
选项:
--vhd
指定 <Disk> 引用虚拟硬盘。
--bare
将磁盘附加到 WSL2,但不要装载它。
--name <Name>
使用装入点的自定义名称装载磁盘。
--type <Type>
装载磁盘时要使用的文件系统(如果未指定)默认为 ext4。
--options <Options>
其他装载选项。
--partition <Index>
要装载的分区的索引(如果未指定)默认为整个磁盘。
这样一来,所有依赖于 /home/user
的配置都会遭到破坏(例如 .zshrc
),需要修改对应的地址为 $HOME
。
对于 conda/forge,它使用了大量写死的地址,因此如果更换用户主目录,则必然会破坏 conda 和 forge 路径。目前似乎没有好的解决方案,个人用这个方案作为替代:
- pyenv 管理多版本 python
- pipx 管理 python 构建的应用程序(比如 PDM 自己)
- pdm 管理项目的依赖,构建发布内容
如果以后遇到问题再进行解决。
问题成因猜测
可能因为我的需求比较小众,也可能我的环境比较特别,我并没有看到与我遇到相似问题的情况。我猜测 wsl 在挂载硬盘的逻辑上仍有一些问题,用 wsl 的 --mount
参数挂载更符合 windows 的逻辑,而在 wsl 里面挂载可能会绕过一些 windows 的逻辑,导致问题的出现。具体问题成因我暂时没有精力分析,这里做个简单记录,如果有人遇到了类似的问题可以进行参考。