標籤:Windows

在 WSL2 中使用 Ubuntu 桌面環境

雖然微軟在 BUILD 2020 上已經宣佈,未來會讓 WSL2 可以執行 GUI 應用程式[1],但不知道什麼時候才會正式支援這個功能,對於想體驗看看效果到底如何的我呢,就打算先在 Windows 端啓動一個 X Window Server 來嚐鮮看看。

事前準備

首先,對於一個習慣 Ubuntu 的我來說,如果可以體驗到完整的 Ubuntu 桌面是再好不過了,換句話說,我們需要在上面可以跑一個完整的 Gnome Shell 環境,並加上 Ubuntu 的 Extension 們。

取得完整 systemd 環境

Gnome Shell 從 3.34 版開始,就已經跟 systemd 整合[2]了,也就是說,我們必須要先有一個完整的 systemd 環境才能順利的執行 Gnome Shell。由於 WSL2 的實作機制其實是啓動一個 Tiny Linux VM,然後利用 Linux Namespace 機制來執行並隔離各個發行版,並且由微軟實作了一個 init (PID 1) 來辦到快速啓動以及作為與 windows 溝通的橋樑[3]

也因此,在 WSL2 的系統裡面,其實是沒有啓用 systemd 的,我們可以簡單的透過下面的方式來檢查看看:

$ systemctl
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
$ ps u -q 1
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0    908   592 ?        Sl   10:31   0:00 /init

可以很明顯的看到,當我們執行 systemctl 的時候,會顯示出我們的 init system (PID 1) 並非 systemd,而是微軟提供的 /init

那我們如果想要擁有一個 systemd 環境的話,該怎麼辦呢?

由於 systemd 必須以 PID 1 的方式執行,所以直接執行 systemd 是沒有用的,但多虧了 Linux Namespace 我們可以在 WSL2 中建立新的 Namespace 並把 systemd 作為 PID 1 來執行,也就是在 WSL2 中再多加一層 PID Namespace,使得我們可以建築一個 systemd 的環境並跳進這個新的 Namespace 中。

所幸,我們不需要自己來研究這部分該如何操作,GitHub 上已經有幾個專案可以直接拿來參考並使用:

我們下面會使用 DamionGans/ubuntu-wsl2-systemd-script 來當作範例,只要按照說明操作就可以了:

$ git clone https://github.com/DamionGans/ubuntu-wsl2-systemd-script.git
Cloning into 'ubuntu-wsl2-systemd-script'...
remote: Enumerating objects: 76, done.
remote: Counting objects: 100% (76/76), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 76 (delta 40), reused 41 (delta 21), pack-reused 0
Unpacking objects: 100% (76/76), 19.46 KiB | 996.00 KiB/s, done.
$ cd ubuntu-wsl2-systemd-script/
ubuntu-wsl2-systemd-script $ bash ubuntu-wsl2-systemd-script.sh
[sudo] password for davy:
Hit:1 http://security.ubuntu.com/ubuntu focal-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu focal InRelease
Hit:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu focal-backports InRelease
Reading package lists... Done
(Reading database ... 31836 files and directories currently installed.)
Preparing to unpack .../0-dbus-user-session_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus-user-session (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../1-dbus-x11_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus-x11 (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../2-dbus_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../3-libdbus-1-3_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking libdbus-1-3:amd64 (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Selecting previously unselected package daemonize.
Preparing to unpack .../4-daemonize_1.7.8-1_amd64.deb ...
Unpacking daemonize (1.7.8-1) ...
Selecting previously unselected package fontconfig.
Preparing to unpack .../5-fontconfig_2.13.1-2ubuntu3_amd64.deb ...
Unpacking fontconfig (2.13.1-2ubuntu3) ...
Setting up fontconfig (2.13.1-2ubuntu3) ...
Regenerating fonts cache... done.
Setting up libdbus-1-3:amd64 (1.12.16-2ubuntu2.1) ...
Setting up dbus (1.12.16-2ubuntu2.1) ...
Setting up daemonize (1.7.8-1) ...
Setting up dbus-x11 (1.12.16-2ubuntu2.1) ...
Setting up dbus-user-session (1.12.16-2ubuntu2.1) ...
Processing triggers for systemd (245.4-4ubuntu3) ...
Processing triggers for man-db (2.9.1-1) ...
Processing triggers for libc-bin (2.31-0ubuntu9) ...
rm: cannot remove '/lib/systemd/system/sysinit.target.wants/proc-sys-fs-binfmt_misc.mount': No such file or directory
'\\wsl$\Ubuntu-20.04\home\davy\ubuntu-wsl2-systemd-script'
是目前用來啟動 CMD.EXE 的目錄路徑。不支援 UNC 路徑。
預設目錄是 Windows 目錄。

成功: 已經儲存指定的值。
'\\wsl$\Ubuntu-20.04\home\davy\ubuntu-wsl2-systemd-script'
是目前用來啟動 CMD.EXE 的目錄路徑。不支援 UNC 路徑。
預設目錄是 Windows 目錄。

成功: 已經儲存指定的值。
ubuntu-wsl2-systemd-script $

安裝完畢後我們需要重啓整個 WSL2,假定我們的 WSL 名稱是 ubuntu-20.04,在命令提示字元(CMD)中執行下列指令以關閉 WSL2:

> wsl.exe -t ubuntu-20.04

接者使用一般使用者啓動 WSL2,就可以發現有不一樣的地方了(多了一個啓動 systemd 的提示),透過檢查 PID 1 也可以發現整個環境已經是由 systemd 掌握了:

Sleeping for 1 second to let systemd settle
Welcome to Ubuntu 20.04 LTS (GNU/Linux 4.19.104-microsoft-standard x86_64)

$ ps u -q 1
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  2.8  0.0 175224 12920 ?        Ss   21:48   0:06 /lib/systemd/systemd --system-unit=basic.target
$

移除用不到的 snap(可選)

由於 Ubuntu 安裝 systemd 時,會連 snap 也一併啓用,如果大家用不到的話可以把 snap 從系統中移除,這麼一來也可以提升啓動速度,我們可以看到系統啓動時也連著一些 snap 的元件一起啓動了(而且還不少 Processes):

$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  1.3  0.0 108788 11360 ?        Ss   22:03   0:00 /lib/systemd/systemd --system-unit=basic.targ
root          43  2.1  0.1  45248 14600 ?        S<s  22:03   0:00 /lib/systemd/systemd-journald
root          61  0.4  0.0  21592  8496 ?        Ss   22:03   0:00 /lib/systemd/systemd-udevd
systemd+      63  1.2  0.0  18548  7896 ?        Ss   22:03   0:00 /lib/systemd/systemd-networkd
root         152  0.0  0.0  10572  4588 pts/0    S    22:03   0:00 /bin/login -p -f      'HOSTTYPE=x86_64' 'PWD=
root         219  1.2  0.0   3608  1792 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/core18_1705.sna
root         220  0.3  0.0   3660  1476 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/lxd_14804.snap
root         221  0.1  0.0   3488  1536 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/snapd_7264.snap
systemd+     228  1.1  0.0  24116 12592 ?        Ss   22:03   0:00 /lib/systemd/systemd-resolved
root         231  0.1  0.0 241020  9280 ?        Ssl  22:03   0:00 /usr/lib/accountsservice/accounts-daemon
message+     232  0.2  0.0   7428  4640 ?        Ss   22:03   0:00 /usr/bin/dbus-daemon --system --address=syste
root         235  0.3  0.1  29216 17788 ?        Ss   22:03   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher
syslog       236  0.1  0.0 224328  4296 ?        Ssl  22:03   0:00 /usr/sbin/rsyslogd -n -iNONE
root         238  2.1  0.2 1457156 35432 ?       Ssl  22:03   0:00 /usr/lib/snapd/snapd
root         240  1.1  0.0  16852  7728 ?        Ss   22:03   0:00 /lib/systemd/systemd-logind
root         261  0.0  0.0 236408  9084 ?        Ssl  22:03   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         302  0.0  0.0   8540  2764 ?        Ss   22:03   0:00 /usr/sbin/cron -f
root         307  0.4  0.1 108036 20512 ?        Ssl  22:03   0:00 /usr/bin/python3 /usr/share/unattended-upgrad
daemon       308  0.0  0.0   3796  2200 ?        Ss   22:03   0:00 /usr/sbin/atd -f
root         318  0.0  0.0   7356  2172 tty1     Ss+  22:03   0:00 /sbin/agetty -o -p -- \u --noclear --keep-bau
root         326  0.0  0.0   5832  1744 ?        Ss   22:03   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
davy         376  0.2  0.0  18444  9612 ?        Ss   22:03   0:00 /lib/systemd/systemd --user
davy         377  0.0  0.0 110132  3188 ?        S    22:03   0:00 (sd-pam)
davy         387  0.2  0.0  10048  4992 pts/0    S    22:03   0:00 -bash
davy         431  0.0  0.0  10604  3224 pts/0    R+   22:04   0:00 ps aux
$

首先我們先將所有 snap 都列出後,一個一個移除:

$ snap list
Name    Version   Rev    Tracking         Publisher   Notes
core18  20200311  1705   latest/stable    canonical✓  base
lxd     4.0.1     14804  latest/stable/…  canonical✓  -
snapd   2.44.3    7264   latest/stable    canonical✓  snapd
# snap remove lxd
# snap remove core18
# snap remove snapd
$ snap list
No snaps are installed yet. Try 'snap install hello-world'.
# apt purge snapd
$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.6  0.0 109692 12692 ?        Ss   22:03   0:03 /lib/systemd/systemd --system-unit=basic.targ
root          43  0.1  0.1  53444 15456 ?        S<s  22:03   0:00 /lib/systemd/systemd-journald
root          61  0.0  0.0  21592  8496 ?        Ss   22:03   0:00 /lib/systemd/systemd-udevd
systemd+      63  0.1  0.0  18548  7896 ?        Ss   22:03   0:00 /lib/systemd/systemd-networkd
root         152  0.0  0.0  10572  4588 pts/0    S    22:03   0:00 /bin/login -p -f      'HOSTTYPE=x86_64' 'PWD=
systemd+     228  0.0  0.0  24116 12592 ?        Ss   22:03   0:00 /lib/systemd/systemd-resolved
root         231  0.0  0.0 241020  9280 ?        Ssl  22:03   0:00 /usr/lib/accountsservice/accounts-daemon
message+     232  0.0  0.0   7428  4640 ?        Ss   22:03   0:00 /usr/bin/dbus-daemon --system --address=syste
root         235  0.0  0.1  29216 17788 ?        Ss   22:03   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher
syslog       236  0.0  0.0 224328  4296 ?        Ssl  22:03   0:00 /usr/sbin/rsyslogd -n -iNONE
root         240  0.1  0.0  16852  7728 ?        Ss   22:03   0:00 /lib/systemd/systemd-logind
root         261  0.0  0.0 236408  9084 ?        Ssl  22:03   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         302  0.0  0.0   8540  2764 ?        Ss   22:03   0:00 /usr/sbin/cron -f
root         307  0.0  0.1 108036 20512 ?        Ssl  22:03   0:00 /usr/bin/python3 /usr/share/unattended-upgrad
daemon       308  0.0  0.0   3796  2200 ?        Ss   22:03   0:00 /usr/sbin/atd -f
root         318  0.0  0.0   7356  2172 tty1     Ss+  22:03   0:00 /sbin/agetty -o -p -- \u --noclear --keep-bau
root         326  0.0  0.0   5832  1744 ?        Ss   22:03   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
davy         376  0.0  0.0  18444  9708 ?        Ss   22:03   0:00 /lib/systemd/systemd --user
davy         377  0.0  0.0 110132  3188 ?        S    22:03   0:00 (sd-pam)
davy         387  0.0  0.0  10180  5252 pts/0    S    22:03   0:00 -bash
root        1168  0.0  0.1 283780 15888 ?        Ssl  22:07   0:00 /usr/lib/packagekit/packagekitd
davy        1273  0.0  0.0  10604  3316 pts/0    R+   22:11   0:00 ps aux

結束後我們就可以發現 Processes 數量減少了許多,讓我們的環境又稍微輕量了些。

在 Windows 準備 X Window Server

這邊有很多選擇,我選擇了 X410[4](付費),大家也可以選擇 VcXsrv[5] 之類的解決方案,這邊就不多贅述了,在這裡的範例中會需要將 X Window Server 開在 Windows 的 6000 port 上。

安裝 Ubuntu 桌面

接著我們就可以來安裝 Ubuntu 的預設桌面了,這裡會需要比較多的硬碟空間:

# apt install ubuntu-desktop
...
10 upgraded, 1077 newly installed, 0 to remove and 64 not upgraded.
Need to get 605 MB of archives.
After this operation, 2260 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
...

安裝結束後,我們需要先取得 Windows 的 IP 位置[6],並嘗試對 X Window Server 連線看看:

$ cat /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.17.160.1
$ nc -v 172.17.160.1 6000
Connection to 172.17.160.1 6000 port [tcp/x11] succeeded!
^C
$

成功連線後,我們可以撰寫一個啓動腳本,將 DISPLAY 指向 Windows 上的 X Window Server 中並且加上一些 Ubuntu 桌面的設定後執行 Gnome Shell:

$ cat - > gnome.sh <<'EOF'
#!/bin/bash
export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0.0;
export XDG_SESSION_TYPE="x11"
export XDG_RUNTIME_DIR=~/.cache/xdg
export XDG_SESSION_CLASS="user"
export XDG_SESSION_DESKTOP=ubuntu
export XDG_CURRENT_DESKTOP=ubuntu:GNOME
export DESKTOP_SESSION=ubuntu
export GDMSESSION=ubuntu
export GNOME_SHELL_SESSION_MODE=ubuntu

gnome-session "$@"
EOF
$ chmod +x gnome.sh
$ ./gnome.sh

Ubuntu Desktop (Gnome Shell) in WSL2

當然,如果想要在這個桌面中執行 Windows CMD 也是沒有問題的,而系統也可以正確的偵測到 Virtualization 是 WSL 呢。

Run CMD in Desktop

關閉 systemd-resolved

這裡是一個可選的項目,由於筆者在透過 /etc/resolv.conf 取得 Windows IP 時有遇到 resolv.conf 被 systemd-resolved 替換成 127.0.0.53 的問題,這邊提供一個方式將這個東西停用,操作完重啓 WSL2 即可:

# systemctl stop systemd-resolved
# systemctl disable systemd-resolved
Removed /etc/systemd/system/multi-user.target.wants/systemd-resolved.service.
Removed /etc/systemd/system/dbus-org.freedesktop.resolve1.service.

後記

由於目前的 WSL2 還沒有 GPGPU 加速,所以拿來執行 GUI 程式的話可能還是會有點 lag,微軟提出的最終方案是透過 RDP 來執行 GUI 應用程式也許會透過 RemoteApp 來將顯卡加速做在 Windows 端,但作為嚐鮮一下,目前的結果已經算是還可以的了XD


  1. 微軟在 BUILD 2020 上宣佈,將會讓 WSL2 可以執行 GUI 應用程式,並使用 Wayland 配合 RDP Protocol 來實作這個功能,詳見微軟部落格〈The Windows Subsystem for Linux BUILD 2020 Summary〉一文 ↩︎

  2. 關於 Gnome Shell 的這個變化,可以參考 Gnome Blog 的〈GNOME 3.34 is now managed using systemd〉一文。 ↩︎

  3. 對於 WSL2 的詳細架構,可以參考 BUILD 2019 的〈The new Windows subsystem for Linux architecture: a deep dive - BRK3068〉議程。 ↩︎

  4. https://x410.dev/ ↩︎

  5. https://sourceforge.net/projects/vcxsrv/ ↩︎

  6. https://docs.microsoft.com/zh-tw/windows/wsl/compare-versions#accessing-network-applications ↩︎

在 WSL2 裡面使用 GPU 加速機器學習

微軟在 Microsoft Build 2020 時曾表示,將在 WSL2 中新增支援 NVIDIA CUDA 以及 DirectML 來讓 Linux 中的機器學習應用可以無痛直接放到 WSL2 中使用。

沒想到才過一個月,微軟就在 Windows 10 Insider (20150+, WSL2 Kernel 4.19.121+) 釋出了支援 NVIDIA CUDA 以及 DirectML 的功能,目前 NVIDIA CUDA 看起來是直接跟 NVIDIA 合作並釋出驅動程式來支援在 WSL2 中的 GPU 虛擬化;而 DirectML 則是跟 NVIDIA/AMD/Intel 合作釋出驅動程式[1],並在 TensorFlow 上面實作以 DirectML 為後端的版本,並向上游提交了 Pull Request[2]

沒想到微軟動作這麼快,看來是真的有在 WSL2 上面投注很多資源想要鞏固整個生態系,對我們這些開發者來說其實也是挺好的,選擇也變多了。XD

ref: https://blogs.windows.com/windowsdeveloper/2020/06/17/gpu-accelerated-ml-training-inside-the-windows-subsystem-for-linux/
ref: https://docs.microsoft.com/zh-tw/windows/win32/direct3d12/gpu-accelerated-training


  1. 截至本文發文時間為止,NVIDIA 僅釋出了可使用 CUDA 的驅動,而 DirectML 的版本則尚未釋出。 ↩︎

  2. https://github.com/tensorflow/community/pull/243 ↩︎

使用 Cmder 直入 WSL

因為工作有 SSH 連線到遠端機器操作的習慣,在換了 Surface Book 2[1] 之後,也還是想要儘量保持這個習慣,一般來說我都是使用 PieTTY[2] 在進行 SSH 連線,不過對於行動裝置我通常會再搭配上 Mosh[3] 使用,可惜在 Windows 上面沒有原生的 Client 支援,於是我打算在 WSL[4] 裡面安裝 Mosh 來使用!

初始規劃

原本我是在找尋可用的 Mosh client,發現 MobaXterm 有支援 mosh,不過試用了一下之後發現他對於鍵盤的 ASCII Keycode 支援沒有很好,一些按鍵是送不出去的,於是就放棄了,打算改轉我在桌機上面的做法——開 Linux 虛擬機再用 PieTTY 連線進入。

不過虛擬機這個念頭很快就被 WSL 取代了(我 Hyper-V 都打開準備要裝系統了XD),因為筆電還是會想要以電量為優先考量,如果裝 VM 的話,電池很快就不夠力了……

於是這邊有兩個想法:

  1. 在 WSL 裡面啓用 SSH Server,用 PieTTY 搭配自動登入[5]來作為 mosh 的執行器
  2. 找一個 PieTTY 的替代品直接接入 WSL Shell 來執行 mosh

由於 WSL 的 lifecycle 只會到所有 shell 關閉就結束,所以如果要用方案 1 的話還必須在 Windows 裡面建立一個 service 來掛著 WSL sshd 的 Deamon(怎麼有種 Docker 的感覺XD),有點小麻煩,雖然我很喜歡,因為 PieTTY 已經用習慣了,其實我沒有很想換 Terminal…… 如果之後用的不順再來研究這個方法XD

使用 Cmder

不過介於我懶惰設定的關係,所以我姑且選擇了方案 2,我找了一個 Windows Terminal Emulator —— Cmder[6],他其實是把 ConEmu[7] 做了一些包裝,使用起來還算是上手,設定一下快捷鍵之後覺得應該還能接受

不過設定 WSL 的地方就需要照著說明來做了,原先我以為進去 cmder 之後,下 ubuntu 啓用 WSL 就完美了,結果在我 ssh/mosh 到遠端機器的時候發現 256 color 的設定不見了,整個顏色都變得很奇怪。

查了一下資料之後發現好像需要 wslbridge 來幫忙轉換一些畫面繪製的指令,這部份其實也不需要自己安裝額外的東西,cmder 包裝的 ConEmu 版本夠新,需要的元件其實本來就有包含了,我們只需要自己加上 Task config。

依照這篇的說明,首先建立一個 Task(名稱隨便取,像我就使用 wsl::bridge),在指令的部分填上如下內容:

set "PATH=%ConEmuBaseDirShort%\wsl;%PATH%" & %ConEmuBaseDirShort%\conemu-cyg-64.exe --wsl -cur_console:pnm:/mnt -t bash -l

然後在 cmder 建立新 Tab 的時候選擇這個 task,就可以得到一個有 256color support 的 Terminal 了!

看看全彩的 lolcat 就覺得……好空虛XDDD

預設進入 mosh

如果想要預設建立某個 mosh 連線的話,也可以複製剛剛的 Task,並在後面加上:-c "mosh [email protected]" 讓 bash 會自動執行 mosh 之後離開。


  1. Surface Book 2 開箱一文 ↩︎

  2. PieTTY ↩︎

  3. Mosh: the mobile shell ↩︎

  4. WSL: Windows Subsystem for Linux ↩︎

  5. 修改 PAM 讓特定 IP 透過 SSH 登入時不需要輸入密碼一文 ↩︎

  6. cmder | Console Emulator ↩︎

  7. ConEmu - Handy Windows Terminal ↩︎