WSL2 从 img 镜像文件启动特定 Linux 发行版完整指南
把任意 Linux 发行版的 raw 镜像备份文件(.img)转换为 WSL2 可用的 VHDX,实现完整桌面环境运行。
背景
WSL2 官方支持的发行版有限(Ubuntu、Debian、Arch 等),但有时我们需要运行一些非标准发行版或自定义镜像------比如 Steam Deck 恢复镜像、树莓派镜像、或自己制作的系统备份。这些镜像通常是 raw 格式的 .img 文件,包含完整的 GPT/MBR 分区表,无法直接被 WSL2 的 --import --vhd 接受。
本文以一个基于 Arch Linux 的定制发行版镜像为例,记录了从 raw .img 文件到在 WSL2 中完整运行(含桌面环境、GPU 加速、用户配置)的全过程。该方法同样适用于其他基于 x86_64 的 Linux 镜像。
适用场景
- 运行非官方 WSL 发行版(如 SteamOS、Kali Rolling、定制 Arch 等)
- 从设备备份镜像中恢复环境进行调试
- 在不刷机的情况下体验特定发行版
- 为嵌入式/IoT 设备镜像做模拟测试
前置条件
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 11 / Windows 10 (Build 19041+) |
| WSL2 | 已安装并启用 |
| 硬件 | 支持 WSLg(大部分现代 Windows 都支持) |
| 磁盘空间 | 至少 20GB 可用 |
| 镜像文件 | 目标 Linux 的 .img 文件 |
| 已有 WSL 发行版 | Ubuntu 或其他(用于运行 qemu-img 转换工具) |
第一步:分析镜像结构
大多数设备镜像包含完整的分区表,而 WSL2 的 --vhd 导入期望的是单文件系统的 VHDX。先检查镜像的分区布局:
bash
# 在已有的 WSL Ubuntu 中操作
# 挂载镜像(不挂载分区)
sudo losetup -fP /mnt/d/path/to/your-image.img
LOOP=$(sudo losetup -j /mnt/d/path/to/your-image.img | awk -F: '{print $1}' | tail -1)
# 查看分区
lsblk "$LOOP"
典型输出:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 7.6G 0 loop
├─loop0p1 259:0 0 64M 0 part # ESP (EFI)
├─loop0p2 259:1 0 128M 0 part # EFI
├─loop0p3 259:2 0 5G 0 part # rootfs (Btrfs/ext4)
├─loop0p4 259:3 0 256M 0 part # var
└─loop0p5 259:4 0 2G 0 part # home (可能加密)
关键判断:如果镜像有多个分区,就不能直接转 VHDX,需要提取 rootfs 重组。
第二步:安装 qemu-img 转换工具
bash
# 在已有的 WSL Ubuntu 中安装
sudo apt update
sudo apt install -y qemu-utils
# 验证
qemu-img --version
第三步:raw 镜像转 ext4 VHDX(核心步骤)
原理
将镜像中的 rootfs(和 var)分区内容提取出来,放入一个新的 ext4 格式 raw 文件,再转换为动态 VHDX。这样 WSL2 拿到的就是一个干净的单分区磁盘。
转换脚本
bash
#!/bin/bash
set -ex
IMG="/mnt/d/path/to/your-image.img"
WORK_DIR="/mnt/d/linux_work"
NEW_RAW="$WORK_DIR/ext4_disk.raw"
MNT_NEW="$WORK_DIR/new_mnt"
MNT_OLD="$WORK_DIR/old_mnt"
MNT_VAR="$WORK_DIR/var_mnt"
OUTPUT="/mnt/d/path/to/your-linux-ext4.vhdx"
mkdir -p "$WORK_DIR" "$MNT_NEW" "$MNT_OLD" "$MNT_VAR"
# 1. 创建 20GB 稀疏 ext4 文件(实际占用极小)
dd if=/dev/zero of="$NEW_RAW" bs=1M count=0 seek=20480
sudo mkfs.ext4 -F "$NEW_RAW"
# 2. 挂载新 ext4 磁盘(读写)
sudo mount -o loop,rw "$NEW_RAW" "$MNT_NEW"
# 3. 挂载原始镜像
sudo losetup -fP "$IMG"
LOOP=$(sudo losetup -j "$IMG" | awk -F: '{print $1}' | tail -1)
# 4. 挂载 rootfs(根据实际文件系统调整)
# Btrfs 示例:sudo mount -o ro,subvol=/ "${LOOP}p3" "$MNT_OLD"
# ext4 示例:
sudo mount -o ro "${LOOP}p3" "$MNT_OLD"
# 5. 复制 rootfs(保留所有属性)
sudo cp -a "$MNT_OLD/"* "$MNT_NEW/"
# 复制隐藏文件
for f in "$MNT_OLD"/.[!.]* "$MNT_OLD"/..?*; do
[ -e "$f" ] && sudo cp -a "$f" "$MNT_NEW/" 2>/dev/null || true
done
# 6. 如果有独立的 var 分区,合并进来
sudo mount -o ro "${LOOP}p4" "$MNT_VAR" 2>/dev/null && {
sudo cp -a "$MNT_VAR/"* "$MNT_NEW/var/" 2>/dev/null || true
} || echo "无独立 var 分区,跳过"
# 7. 创建必要的空目录(WSL 需要这些挂载点)
sudo mkdir -p "$MNT_NEW/dev" "$MNT_NEW/proc" "$MNT_NEW/sys" \
"$MNT_NEW/run" "$MNT_NEW/tmp" "$MNT_NEW/home" \
"$MNT_NEW/mnt"
sudo chmod 1777 "$MNT_NEW/tmp"
sudo chmod 755 "$MNT_NEW/dev" "$MNT_NEW/proc" "$MNT_NEW/mnt"
# 8. 设置基础 DNS
echo "nameserver 8.8.8.8" | sudo tee "$MNT_NEW/etc/resolv.conf"
# 9. 卸载所有
sync
sudo umount "$MNT_NEW" "$MNT_OLD" "$MNT_VAR" 2>/dev/null || true
sudo losetup -d "$LOOP" 2>/dev/null || true
# 10. 扩展到目标大小(稀疏,不占额外空间)
truncate -s 100G "$NEW_RAW"
# 11. 转换为动态 VHDX
qemu-img convert -f raw -O vhdx -o subformat=dynamic "$NEW_RAW" "$OUTPUT"
# 12. 清理
rm -rf "$WORK_DIR"
echo "=== 完成 ==="
qemu-img info "$OUTPUT"
验证
bash
qemu-img info /mnt/d/path/to/your-linux-ext4.vhdx
# image: your-linux-ext4.vhdx
# file format: vhdx
# virtual size: 100 GiB
# disk size: 6.77 GiB ← 实际占用
注意:如果 home 分区是 LUKS 加密的,转换时无法读取。导入后 home 目录为空,需要手动创建用户。
第四步:导入 WSL2
导入命令
在 PowerShell(管理员)中执行:
powershell
# 创建目标目录
New-Item -ItemType Directory -Path 'D:\wsl\myLinux' -Force
# 导入 VHDX
wsl --import myLinux "D:\wsl\myLinux" "D:\path\to\your-linux-ext4.vhdx" --vhd
验证启动
powershell
wsl -d myLinux -- cat /etc/os-release
在线扩展文件系统
导入后 ext4 文件系统可能还是创建时的 20GB,需要扩展到 VHDX 的完整虚拟大小:
bash
# 找到根分区设备名
df /
# 假设是 /dev/sde
# 在线扩展
resize2fs /dev/sde
# 验证
df -h /
第五步:修复 fstab 和基础配置
注释掉不存在的分区引用
镜像中的 /etc/fstab 通常引用硬件分区(如 by-partsets、by-uuid),在 WSL 中不存在:
bash
# 注释掉所有引用硬件分区的条目
sed -i '/by-partsets/s/^/#/' /etc/fstab
sed -i '/by-uuid/s/^/#/' /etc/fstab
# 或者直接备份后清空
cp /etc/fstab /etc/fstab.bak
echo "# fstab managed by WSL" > /etc/fstab
修复 /mnt 目录
某些发行版(特别是基于设备定制的)可能把 /mnt 设为符号链接:
bash
# 检查
ls -la /mnt
# 如果是符号链接(如 -> var/mnt),删除并创建真实目录
rm /mnt
mkdir -p /mnt/wslg /mnt/c /mnt/d
第六步:启用 systemd
在 /etc/wsl.conf 中启用 systemd(WSL2 0.67+ 支持):
bash
cat > /etc/wsl.conf << 'EOF'
[network]
generateResolvConf = false
[boot]
systemd = true
[user]
default = youruser
EOF
修复 DNS
bash
rm -f /etc/resolv.conf
printf "nameserver 8.8.8.8\nnameserver 1.1.1.1\n" > /etc/resolv.conf
重启生效
powershell
wsl --shutdown
wsl -d myLinux
第七步:屏蔽导致 systemd 超时的服务
定制发行版通常预装大量硬件相关服务,在 WSL 容器中毫无意义且会导致 systemd 启动超时(默认 10 秒限制)。
诊断
bash
# 查看 systemd 状态
systemctl is-system-running
# 如果显示 "starting" 或超时,说明有服务阻塞
# 查看等待中的任务
systemctl list-jobs
屏蔽策略
bash
# 切换到多用户目标(不用图形登录)
systemctl set-default multi-user.target
# 屏蔽显示/硬件相关服务(根据实际发行版调整)
for svc in sddm.service gdm.service lightdm.service lxdm.service \
plymouth-quit.service plymouth-quit-wait.service \
firewalld.service NetworkManager-wait-online.service; do
systemctl mask $svc 2>/dev/null
done
# 屏蔽不存在的内核模块
tee /etc/modprobe.d/wsl-blacklist.conf > /dev/null << 'EOF'
install bluetooth /bin/true
install i2c-dev /bin/true
install hwmon /bin/true
EOF
验证
bash
systemctl is-system-running
# running 或 degraded(degraded 可接受,表示有失败的服务但不阻塞)
第八步:配置用户
创建或设置用户
bash
# 如果镜像已有用户(如 deck、pi、ubuntu 等),直接设置密码
echo 'youruser:password' | chpasswd
# 配置免密 sudo
echo 'youruser ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/youruser
chmod 440 /etc/sudoers.d/youruser
# 如果需要创建新用户
useradd -m -G wheel -s /bin/bash youruser
echo 'youruser:password' | chpasswd
创建用户会话自启动
WSL 启动时 systemd 用户会话可能不会自动启动:
bash
# 获取用户 UID
USER_UID=$(id -u youruser)
cat > /etc/systemd/system/autostart-user.service << EOF
[Unit]
Description=Auto-start user session
After=systemd-logind.service
ConditionPathExists=!/run/user/${USER_UID}
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl start user@${USER_UID}
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable autostart-user.service
loginctl enable-linger youruser
配置环境变量
bash
cat > /home/youruser/.bash_profile << 'EOF'
# WSLg 环境
export DISPLAY=:0
export WAYLAND_DISPLAY=wayland-0
export PULSE_SERVER=/mnt/wslg/PulseServer
# 等待 systemd 用户会话
if [ ! -d /run/user/$(id -u) ]; then
sudo systemctl start user@$(id -u) 2>/dev/null
for i in $(seq 1 10); do
[ -d /run/user/$(id -u) ] && break
sleep 1
done
fi
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
EOF
chown youruser:youruser /home/youruser/.bash_profile
启动方式
powershell
# 进入 shell
wsl -d myLinux --user youruser
# 直接执行命令
wsl -d myLinux --user youruser -- bash -lc "dolphin"
推荐始终使用
--user参数,避免 systemd 用户会话启动慢导致的回退到 root。
第九步:启用 WSLg 显示 GUI
完成前面步骤后(特别是修复 /mnt 和启用 systemd),WSLg 会自动挂载:
bash
ls /mnt/wslg/
# distro doc runtime-dir versions.txt weston.log
验证 GUI
bash
export DISPLAY=:0
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir
# 测试(根据发行版安装的软件选择)
kdialog --msgbox "GUI Test!" # KDE
zenity --info --text="GUI Test" # GNOME
xterm # X11 通用
GUI 程序会以独立窗口形式出现在 Windows 桌面上。
第十步:安装 Vulkan D3D12 驱动(GPU 加速)
WSL2 通过 /dev/dxg 接口透传 Windows GPU。需要安装 D3D12 Vulkan 后端驱动(vulkan-dzn,也称 Dozen)才能让 Linux 应用使用 GPU。
Arch 系发行版
bash
sudo pacman -S --noconfirm vulkan-dzn lib32-vulkan-dzn \
directx-headers lib32-directx-headers
Debian/Ubuntu 系
bash
sudo apt install -y mesa-vulkan-drivers libvk-dzn-dev
验证
bash
# 检查 /dev/dxg 是否存在
ls -la /dev/dxg
# 检查 GPU 识别
vulkaninfo --summary | grep -A5 GPU0
输出示例:
GPU0:
apiVersion = 1.2.311
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = Microsoft Direct3D12 (NVIDIA GeForce RTX 5080)
driverName = Dozen
注意 :vulkan-dzn 是非合规 Vulkan 实现(
not a conformant Vulkan implementation),适合应用启动和基本渲染,但不保证游戏级兼容性。
第十一步:配置宿主机代理
如果 Windows 上运行了代理软件,可以在 WSL 中共享:
bash
# 获取宿主机 IP(WSL 网关)
HOST_IP=$(ip route show default | awk '{print $3}')
# 创建代理配置文件
cat > /home/youruser/.proxyrc << EOF
HOST_IP=\$(ip route show default | awk '{print \$3}')
export http_proxy="http://\${HOST_IP}:7897"
export https_proxy="http://\${HOST_IP}:7897"
export all_proxy="socks5://\${HOST_IP}:7897"
export HTTP_PROXY="http://\${HOST_IP}:7897"
export HTTPS_PROXY="http://\${HOST_IP}:7897"
export ALL_PROXY="socks5://\${HOST_IP}:7897"
export no_proxy="localhost,127.0.0.1,::1,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
export NO_PROXY="\$no_proxy"
EOF
# 在 .bash_profile 中加载
echo 'source ~/.proxyrc' >> /home/youruser/.bash_profile
前提:代理软件需开启 "允许局域网连接"(Allow LAN)。
最终配置总览
| 配置项 | 值 |
|---|---|
| 镜像格式 | raw .img → ext4 VHDX(动态扩展) |
| 磁盘 | 100GB 虚拟大小,实际占用约 7GB |
| systemd | 已启用(multi-user.target) |
| GUI | WSLg(Wayland + X11) |
| GPU | vulkan-dzn 透传 Windows GPU |
| 用户 | 非 root 用户 + 免密 sudo |
| 代理 | 共享宿主机端口 |
遇到的坑和解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| WSL 导入报 disk corrupted | VHDX 包含分区表 | 创建单 ext4 文件系统的 VHDX |
| systemd 启动超时 | 硬件服务等待不存在的设备 | 屏蔽无关服务,黑名单内核模块 |
| /mnt/wslg 无法挂载 | /mnt 是符号链接 | 删除链接,创建真实目录 |
| 默认用户是 root | systemd 用户会话启动慢 | 使用 --user 参数 |
| Vulkan 初始化失败 | 缺少 D3D12 后端驱动 | 安装 vulkan-dzn |
| 32 位库缺失 | 发行版未预装 multilib | 手动安装 lib32-* 包 |
| DNS 不工作 | resolv.conf 被自动覆盖 | 设置 generateResolvConf=false |
| 包管理器签名错误 | keyring 未初始化 | 初始化对应发行版的 keyring |
通用性说明
本文虽以 Arch 系定制发行版为例,但方法适用于大多数 x86_64 Linux 镜像:
| 发行版系 | 包管理器 | keyring 初始化 | 注意事项 |
|---|---|---|---|
| Arch 系 | pacman | pacman-key --init + --populate |
需要启用 multilib 仓库安装 32 位库 |
| Debian 系 | apt | 无需 | 需启用 i386 架构:dpkg --add-architecture i386 |
| Fedora 系 | dnf | 无需 | 注意 SELinux 可能需要禁用 |
| 通用 | 无 | 无 | 确保镜像架构为 x86_64 |
关于特定应用场景
上述通用流程同样适用于将游戏主机的系统镜像 导入 WSL。例如,基于 Arch Linux 的游戏设备恢复镜像(如 SteamOS)可以通过此方法在 WSL 中运行,其内置的游戏客户端(如 Steam)会在启动时以设备模式运行,并通过 vulkan-dzn 驱动利用宿主机 GPU。不过由于 WSLg 的合成器限制和驱动合规性问题,该场景更适合开发调试和系统体验,而非实际游戏运行。
游戏客户端中集成的兼容层技术(如基于 Wine 的翻译层)虽然预装在系统中,但在 WSL 环境下受限于不完整的图形栈,无法发挥其在原生 Linux 上的作用------这类技术的核心价值在于让 Linux 用户运行 Windows 程序,而在 WSL(运行在 Windows 之上)中使用反而是多余的。
