WSL2 从 img 镜像文件启动特定 Linux 发行版完整指南

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-partsetsby-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 之上)中使用反而是多余的。

相关推荐
雯宝6 分钟前
3. 驱动框架(块设备)
linux
程序员老陆18 分钟前
Qt中实现窗口真全屏(覆盖Windows任务栏)
开发语言·windows·qt
静止了 所有的花开30 分钟前
Ubuntu 26.04 静态 IP 配置不生效解决方法
linux·ubuntu·静态ip
caimouse2 小时前
ReactOS 图形系统分析(3):显示驱动 DDI framebuf.dll
linux·运维·网络
经典19922 小时前
解析pdf表格内容
windows·pdf
TonyLee0173 小时前
Claude Code + DeepSeek:服务器安装与使用笔记
linux·服务器·deepseek·claude code
buhuizhiyuci3 小时前
【Linux 篇】数字世界的水流蓄水池 —— IO 缓冲区机制深度实战解析
linux·运维·服务器·c++
骇客野人3 小时前
Linux 服务器 Python 版 MCP 服务部署整体方案(适配 Claude/Cursor/自研Agent)
linux·服务器·python
天疆说3 小时前
04 稳定性与性能调优实录:OOM 崩溃、并发槽位、前缀缓存与 DSpark 调参
linux·运维·缓存
星卯教育tony4 小时前
NOI Linux 2.0 服务器多用户网页桌面部署方案(腾讯云轻量Ubuntu20.04专属版 CSP复赛比赛环境搭建免安装虚拟机 )
linux·服务器·腾讯云