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。先检查镜像的分区布局:
# 在已有的 WSL Ubuntu 中操作# 挂载镜像(不挂载分区)sudolosetup-fP/mnt/d/path/to/your-image.imgLOOP=$(sudolosetup-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 转换工具
# 在已有的 WSL Ubuntu 中安装sudoaptupdatesudoaptinstall-yqemu-utils# 验证qemu-img--version第三步:raw 镜像转 ext4 VHDX(核心步骤)
原理
将镜像中的 rootfs(和 var)分区内容提取出来,放入一个新的 ext4 格式 raw 文件,再转换为动态 VHDX。这样 WSL2 拿到的就是一个干净的单分区磁盘。
转换脚本
#!/bin/bashset-exIMG="/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 文件(实际占用极小)ddif=/dev/zeroof="$NEW_RAW"bs=1Mcount=0seek=20480sudomkfs.ext4-F"$NEW_RAW"# 2. 挂载新 ext4 磁盘(读写)sudomount-oloop,rw"$NEW_RAW""$MNT_NEW"# 3. 挂载原始镜像sudolosetup-fP"$IMG"LOOP=$(sudolosetup-j"$IMG"|awk-F:'{print $1}'|tail-1)# 4. 挂载 rootfs(根据实际文件系统调整)# Btrfs 示例:sudo mount -o ro,subvol=/ "${LOOP}p3" "$MNT_OLD"# ext4 示例:sudomount-oro"${LOOP}p3""$MNT_OLD"# 5. 复制 rootfs(保留所有属性)sudocp-a"$MNT_OLD/"*"$MNT_NEW/"# 复制隐藏文件forfin"$MNT_OLD"/.[!.]*"$MNT_OLD"/..?*;do[-e"$f"]&&sudocp-a"$f""$MNT_NEW/"2>/dev/null||truedone# 6. 如果有独立的 var 分区,合并进来sudomount-oro"${LOOP}p4""$MNT_VAR"2>/dev/null&&{sudocp-a"$MNT_VAR/"*"$MNT_NEW/var/"2>/dev/null||true}||echo"无独立 var 分区,跳过"# 7. 创建必要的空目录(WSL 需要这些挂载点)sudomkdir-p"$MNT_NEW/dev""$MNT_NEW/proc""$MNT_NEW/sys"\"$MNT_NEW/run""$MNT_NEW/tmp""$MNT_NEW/home"\"$MNT_NEW/mnt"sudochmod1777"$MNT_NEW/tmp"sudochmod755"$MNT_NEW/dev""$MNT_NEW/proc""$MNT_NEW/mnt"# 8. 设置基础 DNSecho"nameserver 8.8.8.8"|sudotee"$MNT_NEW/etc/resolv.conf"# 9. 卸载所有syncsudoumount"$MNT_NEW""$MNT_OLD""$MNT_VAR"2>/dev/null||truesudolosetup-d"$LOOP"2>/dev/null||true# 10. 扩展到目标大小(稀疏,不占额外空间)truncate-s100G"$NEW_RAW"# 11. 转换为动态 VHDXqemu-img convert-fraw-Ovhdx-osubformat=dynamic"$NEW_RAW""$OUTPUT"# 12. 清理rm-rf"$WORK_DIR"echo"=== 完成 ==="qemu-img info"$OUTPUT"验证
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(管理员)中执行:
# 创建目标目录New-Item-ItemType Directory-Path'D:\wsl\myLinux'-Force# 导入 VHDXwsl--import myLinux"D:\wsl\myLinux""D:\path\to\your-linux-ext4.vhdx"--vhd验证启动
wsl-d myLinux--cat/etc/os-release在线扩展文件系统
导入后 ext4 文件系统可能还是创建时的 20GB,需要扩展到 VHDX 的完整虚拟大小:
# 找到根分区设备名df/# 假设是 /dev/sde# 在线扩展resize2fs /dev/sde# 验证df-h/第五步:修复 fstab 和基础配置
注释掉不存在的分区引用
镜像中的/etc/fstab通常引用硬件分区(如by-partsets、by-uuid),在 WSL 中不存在:
# 注释掉所有引用硬件分区的条目sed-i'/by-partsets/s/^/#/'/etc/fstabsed-i'/by-uuid/s/^/#/'/etc/fstab# 或者直接备份后清空cp/etc/fstab /etc/fstab.bakecho"# fstab managed by WSL">/etc/fstab修复 /mnt 目录
某些发行版(特别是基于设备定制的)可能把/mnt设为符号链接:
# 检查ls-la/mnt# 如果是符号链接(如 -> var/mnt),删除并创建真实目录rm/mntmkdir-p/mnt/wslg /mnt/c /mnt/d第六步:启用 systemd
在/etc/wsl.conf中启用 systemd(WSL2 0.67+ 支持):
cat>/etc/wsl.conf<<'EOF' [network] generateResolvConf = false [boot] systemd = true [user] default = youruser EOF修复 DNS
rm-f/etc/resolv.confprintf"nameserver 8.8.8.8\nnameserver 1.1.1.1\n">/etc/resolv.conf重启生效
wsl--shutdown wsl-d myLinux第七步:屏蔽导致 systemd 超时的服务
定制发行版通常预装大量硬件相关服务,在 WSL 容器中毫无意义且会导致 systemd 启动超时(默认 10 秒限制)。
诊断
# 查看 systemd 状态systemctl is-system-running# 如果显示 "starting" 或超时,说明有服务阻塞# 查看等待中的任务systemctl list-jobs屏蔽策略
# 切换到多用户目标(不用图形登录)systemctl set-default multi-user.target# 屏蔽显示/硬件相关服务(根据实际发行版调整)forsvcinsddm.service gdm.service lightdm.service lxdm.service\plymouth-quit.service plymouth-quit-wait.service\firewalld.service NetworkManager-wait-online.service;dosystemctl mask$svc2>/dev/nulldone# 屏蔽不存在的内核模块tee/etc/modprobe.d/wsl-blacklist.conf>/dev/null<<'EOF' install bluetooth /bin/true install i2c-dev /bin/true install hwmon /bin/true EOF验证
systemctl is-system-running# running 或 degraded(degraded 可接受,表示有失败的服务但不阻塞)第八步:配置用户
创建或设置用户
# 如果镜像已有用户(如 deck、pi、ubuntu 等),直接设置密码echo'youruser:password'|chpasswd# 配置免密 sudoecho'youruser ALL=(ALL) NOPASSWD: ALL'>/etc/sudoers.d/youruserchmod440/etc/sudoers.d/youruser# 如果需要创建新用户useradd-m-Gwheel-s/bin/bash youruserecho'youruser:password'|chpasswd创建用户会话自启动
WSL 启动时 systemd 用户会话可能不会自动启动:
# 获取用户 UIDUSER_UID=$(id-uyouruser)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 EOFsystemctlenableautostart-user.service loginctl enable-linger youruser配置环境变量
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 EOFchownyouruser:youruser /home/youruser/.bash_profile启动方式
# 进入 shellwsl-d myLinux--user youruser# 直接执行命令wsl-d myLinux--user youruser--bash-lc"dolphin"推荐始终使用
--user参数,避免 systemd 用户会话启动慢导致的回退到 root。
第九步:启用 WSLg 显示 GUI
完成前面步骤后(特别是修复/mnt和启用 systemd),WSLg 会自动挂载:
ls/mnt/wslg/# distro doc runtime-dir versions.txt weston.log验证 GUI
exportDISPLAY=:0exportWAYLAND_DISPLAY=wayland-0exportXDG_RUNTIME_DIR=/mnt/wslg/runtime-dir# 测试(根据发行版安装的软件选择)kdialog--msgbox"GUI Test!"# KDEzenity--info--text="GUI Test"# GNOMExterm# X11 通用GUI 程序会以独立窗口形式出现在 Windows 桌面上。
第十步:安装 Vulkan D3D12 驱动(GPU 加速)
WSL2 通过/dev/dxg接口透传 Windows GPU。需要安装 D3D12 Vulkan 后端驱动(vulkan-dzn,也称 Dozen)才能让 Linux 应用使用 GPU。
Arch 系发行版
sudopacman-S--noconfirmvulkan-dzn lib32-vulkan-dzn\directx-headers lib32-directx-headersDebian/Ubuntu 系
sudoaptinstall-ymesa-vulkan-drivers libvk-dzn-dev验证
# 检查 /dev/dxg 是否存在ls-la/dev/dxg# 检查 GPU 识别vulkaninfo--summary|grep-A5GPU0输出示例:
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 中共享:
# 获取宿主机 IP(WSL 网关)HOST_IP=$(iproute show default|awk'{print $3}')# 创建代理配置文件cat>/home/youruser/.proxyrc<<EOF HOST_IP=\$(iproute 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 之上)中使用反而是多余的。