引言
现在的 WSL2 性能损耗已经非常小了,CPU 性能非常接近原生,GPU 计算生态也已较为成熟,WSL2 目前最大的性能损耗几乎都发生在 Windows 与 Linux 的边界上,比如文件系统边界、网络边界、GUI 边界。
从文件系统边界来看,现在的 WSL2 中其实存在着两套完全不同的文件系统路径,一个是以 /mnt/c 为例的 Windows 文件系统,另一个是以 /home 目录为例的 WSL2 Linux 文件系统目录。
前者本质上是在让 Linux 程序直接访问 Windows NTFS 文件系统,其 IO 路径大致为 Linux程序 → WSL文件系统桥接层(drvfs)→ Windows文件API → NTFS,由于 Linux 与 Windows 使用的文件系统语义模型并不相同,尤其是在大量 metadata 操作与小文件 IO 场景下,需要频繁进行 Linux 与 Windows 文件系统语义转换,容易导致编译速度慢或者程序卡顿。
对于后者,也就是 WSL2 的 Linux 文件系统最终其实保存在 ext4.vhdx 这个 Windows 文件中,但在该路径下操作文件时 Linux 不再直接操作 NTFS,此时的 IO 路径变为 Linux程序 → Linux VFS → ext4文件系统 → ext4.vhdx虚拟磁盘 → Windows块设备后端,这里 Windows 不再参与 Linux 文件系统语义转换,Linux 可以以原生方式管理 ext4 文件系统,因此 /home 下的性能通常远好于 /mnt/c。
虽然如此,但 ext4.vhdx 仍然是存储在 Windows 文件里的 Linux 虚拟磁盘,仍然存在虚拟化层,仍然经过 Hyper-V,仍不是直接操作真实块设备,所以它距离真正裸机 Linux 仍然差最后一步。为了实现 WSL2 进一步的性能突破,本文介绍如何实现在 WSL2 中挂载物理磁盘。
挂载磁盘到 WSL2
注意:要挂载的磁盘分区不能与 C 盘在同一块物理磁盘上。
首先查看自己插入的磁盘在 Windows 中对应的磁盘编号:
(base) PS C:\WINDOWS\System32\WindowsPowerShell\v1.0> Get-DiskNumber Friendly Name Serial Number HealthStatus OperationalStatus Total Size PartitionStyle
------ ------------- ------------- ------------ ----------------- ---------- ----------
0 XPG GAMMIX... 0000_0000_0000_0000_707C_1800... Healthy Online 953.87 GB GPT
1 Realtek RT... 0000000000000000 Healthy Offline 931.51 GB GPT
比如在这里我所插入的磁盘对应编号 1,就可以通过以下命令将该磁盘挂载到 WSL2 中:
wsl --mount \\.\PHYSICALDRIVE1
该命令只挂载磁盘,并不启动 WSL2。
为了防止磁盘编号发生变化,同时方便每次自动化挂载磁盘并启动 WSL2,这里通过自动化脚本提供快捷方式:
首先创建后缀为 .ps1 的 PowerShell 脚本文件,加入以下内容:
# 自动挂载指定移动硬盘并进入 WSL
$disk = Get-Disk | Where-Object {$_.FriendlyName -like "*Realtek RTL9210 NVME*"
} | Select-Object -First 1if ($disk) {$physicalDrive = "\\.\PHYSICALDRIVE$($disk.Number)"Write-Host "Mounting disk $($disk.Number)..."$output = wsl --mount $physicalDrive --bare 2>&1if ($LASTEXITCODE -eq 0) {Write-Host "Disk attached successfully."}else {# 如果返回值非0说明大概率已经挂载,倘若未成功挂载依靠.bashrc文件内逻辑判断Write-Host "Disk already attached."}Write-Host "Enter Ubuntu-22.04..."wsl -d Ubuntu-22.04
} else {Write-Host "Disk not found."
}
在桌面右键,新建快捷方式,对象的位置中输入 powershell -NoExit -ExecutionPolicy Bypass -File "D:\WSL\ubuntu20_automount.ps1" ,其中 -File 后跟的是刚刚创建的脚本路径,创建完成后将该快捷方式复制到 C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs 目录下即可在开始菜单找到该快捷方式,点击即可自动挂载磁盘并启动 WSL2。
以上第一步只是将把 Windows 的物理磁盘接入 WSL2 虚拟机,但是还不能访问文件,因为 Linux 还没挂载文件系统。接下来介绍如何在 Linux 挂载文件系统。
查看子系统中的磁盘分区
在通过 wsl --mount ... 命令将磁盘挂载到 WSL2 后,在所有 Linux 子系统的 /dev 目录下都能发现对应磁盘。
要知道该目录下哪里是刚刚挂载的磁盘,可以通过以下命令:
long@wsl-ubuntu22:/dev$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 388.6M 1 disk
sdb 8:16 0 186M 1 disk
sdc 8:32 0 32G 0 disk [SWAP]
sdd 8:48 0 931.5G 0 disk
├─sdd1 8:49 0 431.5G 0 part /home/long/disk
└─sdd2 8:50 0 500G 0 part
sde 8:64 0 1T 0 disk /mnt/wslg/distro/
可以看到,sdd 是我挂载的整块磁盘,sdd1 和 sdd2 是该磁盘划分的两块分区,而在 /dev目录下,可以看到 sdd、sdd1、sdd2 文件,这里 sdd1 是我所需要挂载的磁盘分区。但是由于 sdd 名称是动态分配的,不稳定,因此这里建议通过更稳定的 uuid 进行挂载。
要查看磁盘的 uuid,可以进入到 /dev/disk 目录下:
long@wsl-ubuntu22:/dev/disk$ ls
by-id by-partlabel by-partuuid by-path by-uuid
在该目录下有五个子目录,本质上都是 udev 为同一块分区创建的不同“索引方式”的符号链接。可以通过如下命令查看这些链接实际上对应的哪些分区:
long@wsl-ubuntu22:/dev/disk$ ls -l /dev/disk/by-uuid
total 0
lrwxrwxrwx 1 root root 10 May 23 20:29 0F1B-0CBA -> ../../sdd2
lrwxrwxrwx 1 root root 9 May 23 20:29 399d8295-881e-4617-9b23-52a65395f4a3 -> ../../sde
lrwxrwxrwx 1 root root 9 May 23 20:29 657c8056-9c5a-4782-acc1-28913e68e446 -> ../../sdc
lrwxrwxrwx 1 root root 10 May 23 20:29 868ef4c5-de02-674f-920b-9a4ab3a4a09d -> ../../sdd1
其中通过 uuid 索引最为稳定,因此推荐通过 uuid 挂载磁盘分区。
挂载分区到文件夹
现在已经将磁盘挂载到 WSL2 且知道磁盘分区的 uuid 就可以挂载到文件夹了:
sudo mount "UUID=$UUID" "$MOUNTPOINT"
为了避免每次进入 WSL2 都手动挂载系统,在 ~/.bashrc 文件下添加以下命令,以便每次进入 WSL 能自动挂载分区:
UUID="868ef4c5-de02-674f-920b-9a4ab3a4a09d"
MOUNTPOINT="/home/long/disk"if mountpoint -q "$MOUNTPOINT"; thenecho "已挂载硬盘到 $MOUNTPOINT ✅"
elif [ -e "/dev/disk/by-uuid/$UUID" ]; thensudo mount "UUID=$UUID" "$MOUNTPOINT" && \echo "已挂载硬盘到 $MOUNTPOINT ✅" || \echo "挂载失败 ❌"
elseecho "硬盘尚未接入 WSL ❌"
fi
其中 UUID 和 MOUNTPOINT 替换成对应磁盘分区和挂载点。
