WSL 使用技巧

Windows Subsystem for Linux 是 Windows 上运行 Linux 环境的兼容层。WSL 有两个版本,分别是 WSL 1 和 WSL 2,后者现在是 Windows 默认安装的版本。两个版本在底层实现上有很大区别,WSL 1 通过 lxss.syslxcore.sys 这两个 Windows 内核驱动来模拟 Linux 内核,而 WSL 2 通过 Hyper-V 虚拟机托管运行了一个经过优化过的 Linux 内核,能够提供完整的 Linux 系统调用,并支持 systemd 和 IPv6。缺点是跨系统的文件系统访问性能比 WSL 1 差很多(但 WSL 2 的文件 IO 性能比 WSL 1 的更好)。因此,当使用 WSL 作为开发环境时,建议不要将源代码存放在 Windows 的文件系统下

WSL 2 的其他问题有:

本文使用的 WSL 环境如下:

WSL 版本: 2.1.5.0
内核版本: 5.15.146.1-2
WSLg 版本: 1.0.60
MSRDC 版本: 1.2.5105
Direct3D 版本: 1.611.1-81528511
DXCore 版本: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windows 版本: 10.0.19045.4170

WSL 命令

通过 wsl 命令可以启动进入 WSL 环境;通过 wsl --install 命令可以从 WSL 1 升级到 WSL 2;还可以通过 wsl --set-verion 命令在 WSL 1和 WSL 2 版本间切换;使用 wsl --update 用于更新 WSL 版本;通过 wsl -l --all -v 查看所有 WSL 发行版及其状态。

除了以上这些常用命令,还有如下的一些不常用但比较实用的命令:

以指定用户身份运行 WSL

通过参数 -u 可以指定运行 WSL 的用户身份。比如要以 root 身份来运行 WSL:

POWERSHELLwsl -u root

Windows 下运行 Linux 命令

可以使用 wsl <linux command> 来运行 Linux 命令。例如可以用如下命令来运行带参数的 Linux Shell 命令 ls -al

POWERSHELLwsl ls -al

可以使用 -u 搭配 -- 参数,以指定用户身份运行 Linux 命令。例如:

POWERSHELLwsl -u root -- apt update

终止正在运行的 WSL

终止所有正在运行的发行版和 WSL 2:

POWERSHELLwsl --shutdown

终止指定的分发版:

POWERSHELLwsl -t <Distro>

其中 Distro 是发行版名称,比如 wsl -t Ubuntu

互相访问文件系统

在 Windows 中访问 Linux 文件系统的路径为:

\\wsl$\<Distro>\

或者

\\wsl.localhost\<Distro>\

其中 Distro 是发行版名称,比如 \\wsl$\Ubuntu\ 或者 \\wsl.localhost\Ubuntu\

在 Linux 中访问 Windows 文件系统的路径为:

/mnt/<Drive>/

其中 Drive 是 Windows 的盘符,比如 /mnt/c/ 或者 /mnt/d/。通过 mount 命令可以得知,在 WSL 2 中, /mnt/c/ 的文件类型为 9p

C:\ on /mnt/c type 9p (rw,noatime,dirsync,aname=drvfs;path=C:\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=5,wfd=5)

不同于 WSL 1 的 drvfs,9pfs 是一种远程文件协议。这也解释了为什么 WSL 2 中,访问 Windows 文件系统的性能会比较差了。

WSL 配置文件

WSL 的配置文件有两处,分别是:

  1. Linux 系统下的 /etc/wsl.conf
  2. Windows 系统下的 %UserProfile%\.wslconfig

wsl.conf 仅对当前 Linux 系统生效;.wslconfig 作用范围是全局,但仅有 WSL 2 支持该配置文件。

修改配置文件后,需要重启 WSL 实例使得修改生效:

POWERSHELLwsl --shutdown && wsl

更多的配置信息可以参考:https://learn.microsoft.com/zh-cn/windows/wsl/wsl-config

启用 systemd

在 WSL 配置文件中添加如下内容:

INI[boot]
systemd = true

禁止 WSL 和 Windows 交互

默认情况下,WSL 中可以执行 Windows 程序。其底层利用 binfmt_misc 实现:当 Linux 执行 PE 文件时,匹配到 magic 4d5a ,会交给 /init 运行。事实上,WSL 中执行的 Windows 程序,最终是都在 Windows 系统中执行的。而 /init 会和宿主 Windows 系统进行交互,传递执行参数并将执行结果返回到 WSL 中。

此外,WSL 还会将 Windows 环境变量 PATH 中的路径添加到 Linux 的 path 环境变量中。如果 Linux 系统和 Windows 系统中有相同文件名的可执行文件,这一特性某些情况下会导致冲突,在前文《在 WSL 中运行 Lazarus IDE》中描述了这个问题。

在 WSL 配置文件中添加如下内容:

INI[interop]
enable = false # 关闭执行 Windows 程序的功能
appendWindowsPath = false # 不允许添加 Windows 的可执行路径到 path 中

通过下面的命令可以在当前会话中临时关闭执行 Windows 程序的功能:

BASHecho 0 > /proc/sys/fs/binfmt_misc/WSLInterop

通过下面的命令开启:

BASHecho 1 > /proc/sys/fs/binfmt_misc/WSLInterop

共享环境变量

WSL 和 Windows 共享一个名为 WSLENV 的特殊环境变量,其值保存着需要在 WSL 和 Windows 之间共享的环境变量列表。列表的规则如下:

比如,我们需要在 WSL 中使用 Windows 的环境变量 JAVA_HOME,可以在 Windows 中如下操作:

POWERSHELL$ENV:WSLENV += ':JAVA_HOME/p'

假设 Windows 下的 JAVA_HOME 值为 C:\jre,则 WSL 中 JAVA_HOME 值会自动转换为 /mnt/c/jre

网络设置

WSL 1 使用和 Windows 共用相同的网络接口,WSL 和 Windows 应用之间可视作本机访问。而 WSL 2 底层为托管虚拟机,因此存在跨主机网络访问的问题。默认情况下,WSL 2 使用 NAT 模式的网络结构。对 Windows 11 版本 22H2 以上的系统,可以开启网络镜像模式。此模式下,会将 Windows 下的网络接口「镜像」到 WSL 的 Linux 环境中。

启用「镜像」网络模式

在 WSL 全局配置文件 .wslconfig 中添加以下内容:

INI[wsl2]
networkingMode = mirrored
dnsTunneling = true
firewall = true

NAT 模式下的 WSL 和 Windows 网络

在 NAT 模式下,Linux 系统默认使用动态 IP。不过当 Linux 进程绑定端口时,WSL 会将端口映射到 Windows 中。底层是通过 wslrelay.exe 进程实现了端口转发。因此,Windows 下可以通过本机端口来访问 WSL 下的网络端口。通常情况下,我们而不需要获取 Linux 主机的 IP 地址。WSL 下可以通过下面的命令来获取 Linux 主机的 IP 地址:

BASHhostname -I

Windows 下则是:

POWERSHELLwsl hostname -I

而在 WSL 下要访问 Windows 主机则需要通过 IP 地址。在 Windows 中,可以查看网络适配器 “vEthernet (WSL)” 的 IPv4 地址来获得 Windows 主机的 IP。在 Linux 环境中,也可以通过下面的命令来获取 Windows 主机的 IP:

BASHip route show | grep -i default | awk '{ print $3}'

需要注意的是,在没有开启镜像模式的 WSL 2 环境中,使用 localhost 地址是无法访问到 Windows 网络服务的。 另外,必须正确设置 Windows 防火墙,才能让 WSL 2 下的 Linux 进程能够访问到 Windows 主机的端口。参见:Add “allow” rule to Windows firewall for WSL2 network · Issue #4585

为 WSL 设置代理

对于 Windows 11 操作系统,可以在 WSL 全局配置文件 .wslconfig 中添加以下内容,让 WSL 使用 Windows 的系统代理服务器设置:

INI[wsl2]
autoProxy = true

如果系统代理服务器使用的是本机地址,则需要开启网络镜像模式。

如果是 Windows 10 操作系统,则需要手动设置代理服务器。假设使用的代理服务器运行在 Windows 宿主的 7890 端口上,新建配置脚本 /etc/profile.d/proxy.sh,内容如下:

SHexport https_proxy=`ip route show | grep -i default | awk '{ printf "http://%s:7890", $3}'`
export http_proxy=`ip route show | grep -i default | awk '{ printf "http://%s:7890", $3}'`

另外,需要在 Windows 中新增一条防火墙规则,允许来自 WSL 的网络访问本地代理服务器端口。以管理员权限在 Windows 下运行以下命令:

POWERSHELLNew-NetFirewallRule -DisplayName "Local Proxy" -Direction Inbound -Protocol TCP -LocalPort 7890  -Action Allow

迁移 WSL 发行版镜像文件

WSL 发行版默认安装路径是 %UserProfile%\AppData\Local\Packages\<发行版名称> 下,发行版镜像文件则保存在安装路径下的 LocalState\ext4.vhdx 文件中。可以使用下面的命令将镜像文件迁移到其他路径(此处以 D:\WSL\Ubuntu.vhdx 为例):

POWERSHELLwsl --shutdown
wsl --export Ubuntu D:\WSL\Ubuntu.vhdx --vhd
wsl --unregister Ubuntu
wsl --import-in-place Ubuntu D:\WSL\Ubuntu.vhdx

重新导入后,WSL 会以 root 用户身份登录,可以重新设置默认登录用户:

POWERSHELLubuntu config --default-user <YourUserName>

VSCode 远程开发扩展包

安装 Remote Development 扩展包,让 VSCode 支持使用 WSL 开发环境。有两种方法可以启动远程开发:

  1. 在 WSL 命令行下,运行命令:
    BASHcode ~\myproject
  2. 在 VSCode 中用 Ctrl+Shift+P 打开命令面板,输入 WSL,点击 WSL: Connect to WSL

方法一需要开启 WSL 和 Windows 交互功能。

配置中文环境

编辑文件 /etc/locale.gen,移除 # zh_CN.UTF-8 UTF-8 行首的注释;编辑文件 /etc/default/locale,添加一行内容:

INILANG=zh_CN.UTF-8

运行下面命令生成本地信息:

BASHsudo locale-gen

也可以运行 sudo dpkg-reconfigure locales 进行配置。

运行 Linux GUI 程序

WSL 2 自带 WSLg 组件可以让 WSL 中的 GUI 应用直接在 Windows 中进行渲染。WSLg 提供了 Wayland, X Server 和 PulseAudio 服务,底层通过微软的 RDP 协议和 Windows 连接,具体由 msrdc.exe 进程实现。

也可以使用其他第三方 X Server 服务。推荐 MobaXterm 自带的 X Server,当使用 MobaXterm 启动 WSL 环境时,会自动配置好环境变量 DISPLAY 的值,而无需手动设置。

如果无法运行 Linux 下的 GUI 程序,请确认当前环境是否存在如下环境变量:

WSL2_GUI_APPS_ENABLED=1
WAYLAND_DISPLAY=wayland-0
DISPLAY=:0

可以在 WSL 全局配置文件 .wslconfig 中关闭这一特性:

INI[wsl2]
guiApplications = false

如果是 WSL 1 则必须要第三方的 X Server 支持。

安装中文字体

WSL 的 Ubuntu 默认没有安装中文。为了让 GUI 应用显示中文,至少需要安装一款中文字体:

BASHsudo apt install fonts-noto-cjk

也可以共享 Windows 字体:

BASHsudo apt install fontconfig

新建配置文件 /etc/fonts/local.conf 并保存如下内容:

XML<fontconfig>
  <dir>/mnt/c/Windows/Fonts/</dir>
</fontconfig>

不过考虑到 WSL 下访问 Windows 文件系统性能较差,也许共享 Windows 字体并不是一个好主意。

安装中文输入法

不知什么原因,尝试了安装 IBus 输入法也启动了 ibus-daemon,但输入法仍无法使用。推荐安装 fcitx 输入法:

BASHsudo apt-get install fcitx-pinyin

新建配置脚本 /etc/profile.d/fcitx-ime.sh,内容如下:

SHexport XMODIFIERS=@im=fcitx 
export GTK_IM_MODULE=fcitx 
export QT_IM_MODULE=fcitx
export DefaultIMModule=fcitx

可以运行命令 fcitx-configtool 进行输入法配置。最后启动 fcitx 进程:

BASHfcitx &>/dev/null

snap 安装的应用无法使用 fcitx 输入法,这个问题似乎至今还未解决。这里推荐使用 flathub 安装应用。

KDE 应用图标丢失

使用 apt 安装的 KDE 应用(诸如 dolphin, konsole, kate 等)不显示图标。解决方法如下:

  1. 安装 qt5ct

    BASHsudo apt install qt5ct
  2. 设置环境变量

    BASHexport QT_QPA_PLATFORMTHEME=qt5ct

环境变量可以在配置文件 ~/.bashrc~/.profile 中持久化设置

修复 “qt.qpa.xcb: could not connect to display”

引起这个错误的原因有很多。首先可以尝试安装以下包来修复这个问题:

BASHsudo apt install libxcb-xinerama0 libqt5x11extras5

不过通常情况下,Ubuntu 已经默认安装了这两个包。因此上面的方案解决问题的概率很小。

下面是 WSLg 运行来自 flathub 的 Dolphin 时所提示的错误:

qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: eglfs, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, xcb.

由于 WSLg 使用的是 wayland 协议,可以通过设置如下的环境变量来解决问题:

BASHexport QT_QPA_PLATFORM=wayland

或者通过运行参数来指定环境变量:

BASHflatpak run --env=QT_QPA_PLATFORM=wayland org.kde.dolphin

奇怪的是,其他来自 flathub 的 KDE 应用(也是基于 Qt)都运行正常。这可能和应用默认打包的参数有关吧。

The plugins can be described as follows:

  • Linux plugins - those use Linux-specific input devices and various output devices
    • wayland (since Qt 5.11) - Allows to run Qt applications on Wayland display servers.
    • eglfs - Uses the OpenGL ES in fullscreen mode. There’s no other way since OpenGL has no concept of a window manager.
    • directfb (not directfp) - Uses the linux frame buffer with OpenGL ES via the directfb layer (see also wikipedia). Integrates into the directfb windowing.
    • linuxfb - Uses the linux frame buffer in fullscreen mode. There’s no other way since linuxfb has no concept of a window manager.
    • kms - Uses linux kernel modesetting API in fullscreen mode. There’s no other way since DRM has no concept of a window manager.
    • openwfd - Uses an openwfd Wifi display in fullscreen mode. There’s no other way since openwfd has no concept of a window manager.
  • Platform-independent plugins - could be made to run on any OS
    • xcb - Runs on an X11 server and is integrated into the X11 windowing environment. Generally it won’t behave correctly without a window manager running as well. Can be made to work on Windows, given a Windows implementation of xlib, if you want to, say, serve applications from a Windows server to X11 thin terminals (typically Unix boxes).
    • offscreen - Renders to an offscreen buffer. Useful for rendering to custom displays.
    • minimal - A minimalistic backing store that optionally dumps the virtual screen to a file. Implements the bare minimum of functionality just to demonstrate how to start writing a platform plugin.
  • Other platform-specific plugins
    • android - Uses the Android APIs and is integrated into the Android environment.
    • windows - Uses the WINAPI and is integrated into the Windows windowing environment.
    • cocoa - Uses the Cocoa APIs and is integrated into the OS X windowing environment.
    • iOS - Uses the iOS toolkits and is integrated into the iOS environment.
    • qnx - Uses the QNX APIs and is integrated into the QNX photon windowing environment.