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

相关推荐
瓶中怪3 小时前
ROS2 机器人软件系统
linux·c++·python·ubuntu·vmware·ros2·机器人软件开发
iangyu3 小时前
linux配置时间同步
linux·运维·服务器
天空'之城3 小时前
Linux 系统编程 04:进程基础
linux·开发语言·进程基础
从零开始的代码生活_3 小时前
NAT、代理服务与内网穿透详解
linux·服务器·网络·c++·http·智能路由器
灯厂码农3 小时前
C语言内存管理——内存对齐与共用体union
linux·服务器·c语言
charlie1145141914 小时前
Cinux: 加载第一个内核:从 bootloader 跳进 C++
linux·开发语言·c++·嵌入式
iCxhust5 小时前
linux目录是否保存在硬盘 启动后读入解析的
linux·运维·服务器
懒鸟一枚5 小时前
Linux 系统 Service 服务配置详解
linux·服务器·网络
RisunJan6 小时前
Linux命令-readonly(Bash 内建设置只读变量)
linux