Windows で VMware Workstation Player を使って Linux ベースの独自 OS (以下 GCD OS と呼ぶ) を動かしていたのだけど、 WSL2 (Windows Subsystem for Linux 2) が物理ディスクをマウントできるようになったと聞き、 WSL2 が VMware の代りに使えるか試してみた。 ただし、物理ディスクをマウントするには Windows 11 ビルド 22000 以上が必要。
本当は Linux カーネルも自前でビルドしたものを使いたいが、 とりあえず WSL2 のカーネルをそのまま使い、 root ファイルシステムは物理ディスク上にインストールしてある GCD OS を使う方向で考えた。
以前、 OpenVZ な VPS サービス上で GCD OS を動かしたことがある (10年前!) ので、 その時と同様に chroot して物理ディスク上の GCD OS を起動するのが簡単そう。 つまり、 以下のシェルスクリプト gcd.sh を WSL2 上で実行するだけ:
#!/bin/sh PATH="/mnt/c/WINDOWS/System32:/usr/bin:/bin:/usr/sbin:/sbin" ROOT="/usr/local/GCD" cd /mnt/c wsl.exe --mount '\\.\PHYSICALDRIVE1' --bare test -d $ROOT || mkdir $ROOT mount LABEL=/ $ROOT $ROOT/etc/init.d/chroot
このスクリプトは WSL2 の仮想ディスク (Virtual Hard Disk) 内に置いてもよいが、 私は Windows の C: ドライブに置いた。 WSL2 の内容を変更する必要がなくなるし、 WSL2 でどのディストリビューションを使っているかに依存しなくなる。 仮想ディスクが肥大化したら、インストールし直して初期状態に戻してもよい。
例えば C:\bin\gcd.sh に置いて、 次のように実行する:
C:\Windows\System32\wsl.exe -u root -- /mnt/c/bin/gcd.sh
タスクスケジューラーで Windows の起動時に自動的に実行すれば、 Windows にログインしなくても外部から ssh で GCD OS へログインできるので便利。 VMware より軽くて手軽で便利かも?
以下、このスクリプト gcd.sh を順に説明する:
まず、 「wsl.exe --mount '\\.\PHYSICALDRIVE1' --bare」 で WSL2 から物理ディスク 「PHYSICALDRIVE1」 が見えるようにする。 wsl.exe は Windows のコマンドだが、 WSL2 は Linux なのに /mnt/c にマウントされた C: ドライブ上の Windows のコマンドが実行できる (Linux カーネルの binfmt_misc を使っている)。
(Linux の) lsblk コマンドを使うと、 物理ディスクが見えることが確認できる:
Linux 5.10.16.3-microsoft-standard-WSL2. kayano:~ $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 256G 0 disk sdb 8:16 0 339.7M 1 disk sdc 8:32 0 256G 0 disk /mnt/virtual sdd 8:48 0 7.3T 0 disk ├─sdd5 8:53 0 200G 0 part ├─sdd6 8:54 0 16G 0 part [SWAP] └─sdd7 8:55 0 6.8T 0 part /
sdd が WSL2 から見えるようになった物理ディスク (8TB HDD)。 パーティションが 3つあることが分かる。 うち sdd7 が GCD OS の root ファイルシステム。
一点注意すべきなのは、 Windows のシステムドライブ 「PHYSICALDRIVE0」 は指定できないので、 システムドライブとは異なる物理ディスクを使う必要があるという点。 8TB HDD など大容量のハードディスクを、 パーティションで区切って Windows と Linux の両方をインストールしている人は多いと思うのだけど、 残念ながら Windows と同じディスク上にある Linux パーティションは、 WSL2 から使うことはできない。 VMware ならできるのに...
仕方がないので私は (わざわざ) M.2 NVMe SSD を買って Windows のシステムドライブを NVMe へ移し、 SATA0 につないだ 8TB HDD を (Windows をインストールしていたパーティション 1〜4 を削除して) Linux 専用にした。
また、WSL2 上で wsl.exe を実行するとき、 カレントディレクトリが WSL2 の仮想ディスクだと、 どんな引数でも常に 「Invalid argument」 エラーになる:
root@kayano:~# /mnt/c/Windows/System32/wsl.exe --shutdown /mnt/c/Windows/System32/wsl.exe: Invalid argument root@kayano:~#
エラー出力後の改行がうまくいかないあたり、 いかにもバグっぽい?
カレントディレクトリが C: ドライブだと正常に実行できるようなので、 wsl.exe を実行する前に 「cd /mnt/c」 を行っている。
物理ディスクが WSL2 で見えるようになったら、 次にこの物理ディスクをマウントする: 「mount LABEL=/ $ROOT」。 ここでは LABEL が 「/」 のパーティションを 「$ROOT」 つまり /usr/local/GCD へマウントしている。
あとは GCD OS を起動するだけ: 「$ROOT/etc/init.d/chroot」。 /etc/init.d/chroot は OpenVZ 上で GCD OS を起動するときも使った、 以下のシェルスクリプト:
#!/bin/sh root=`echo $0 | sed -e 's@/etc/init.d/chroot$@@'` if [ ! -d $root ]; then echo "Can't find root: $root" exit 1 fi sed -n 's/^[a-z][_a-z]* \([^ ][^ ]*\) .*/\1/p' < /proc/mounts | while read d; do if [ -d "$d" ]; then test -d "$root$d" || mkdir "$root$d" mount -obind "$d" "$root$d" elif [ -f "$d" ]; then test -f "$root$d" || touch "$root$d" mount -obind "$d" "$root$d" fi done if [ -d /lib/modules/`uname -r` ]; then mount -obind /lib/modules $root/boot/lib/modules fi chroot $root /bin/sh <<EOF swapon -a mount -a -t ext4 /etc/init.d/svscanboot & /etc/rc.d/rc.M EOF
このシェルスクリプトは、 まず WSL2 の /proc/mounts を参照して、 WSL2 がマウントしているディレクトリとファイルを、 そのまま GCD OS のルート (/usr/local/GCD) へマウントする。 これで GCD OS でも /mnt/c にマウントされた Windows コマンドを実行できるようになる。
次に 「chroot /usr/local/GCD /bin/sh」 を実行して、 chroot 環境下で svscanboot と /etc/rc.d/rc.M を実行する。 svscanboot は daemontools の起動スクリプト。 GCD OS のほとんどのデーモン類は daemontools の管理下で起動される。 一方 /etc/rc.d/rc.M は、 GCD OS のブートスクリプトで、 ネットワーク等の各種設定と、 一部のデーモン類の起動を行なう。
ssh サーバや WWW サーバも /etc/rc.d/rc.M が立ち上げるが、 いままで WSL2 のネットワークは Windows 内でしか見えないバーチャルネットワーク (内部ネットワーク) だったらしい。 「WSL2 外部から接続」 などとググっても、 外部から WSL2 の ssh サーバにログインするには (netsh.exe の) portproxy を使え、という話ばかり出てくる。
どのバージョンから可能になったのかは知らないが、 「仮想スイッチ マネージャー」 で設定すれば VMware のようなブリッジモードが WSL2 でも使えるようになる。
まず Windows 管理ツールの 「Hyper-V マネージャー」 を実行する。 仮想マシン (この例では KAYANO) を選択し、 「操作(A)」 メニューから 「仮想スイッチ マネージャー(C)...」 を選ぶと、 「仮想スイッチ マネージャー」のウィンドウが開く。
左ペイン 「仮想スイッチ」 の中から 「WSL」 を選び、 右ペインに表示される 「接続の種類」 として 「外部ネットワーク(E):」を選択し、 適切なネットワーク アダプターを選択する。
これで WSL2 が外部ネットワークと通信できるようになる。 (いまのところ) タグVLAN が使えるようにはできていないが、 VMware ではタグVLAN が使えるので、 なんとかしてタグVLAN が使えるようにしたいところ...
他のマシン (以下の例では senri) から ssh で GCD OS (kayano) へログインしてみる:
senri:/ # ssh kayano Last login: Wed May 11 04:16:34 2022 from senri.gcd.org Linux 5.10.16.3-microsoft-standard-WSL2. kayano:~ # ip addr show dev eth0 6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000 link/ether 00:15:5d:ff:11:b1 brd ff:ff:ff:ff:ff:ff inet 192.168.18.40/24 brd 192.168.18.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:feff:11b1/64 scope link valid_lft forever preferred_lft forever kayano:~ # tcpspray senri Transmitted 102400 bytes in 0.000315 seconds (317460.317 kbytes/s) kayano:~ # free total used free shared buffers cached Mem: 8055704 573892 7481812 0 30704 178088 -/+ buffers/cache: 365100 7690604 Swap: 18874364 0 18874364 kayano:~ # chroot_escape /bin/bash groups: cannot find name for group ID 11 groups: cannot find name for group ID 14 root@kayano:/# lsb_release -d Description: Ubuntu 20.04.4 LTS root@kayano:/# exit kayano:~ #
chroot 環境から脱出 (chroot_escape /bin/bash) すると WSL2 の Ubuntu 環境に戻る。 で、exit すると GCD OS に戻る。
この GCD OS で物理ディスクの読み書き速度を測ってみた:
kayano:/ # hdparm -t /dev/sdd7 /dev/sdd7: Timing buffered disk reads: 538 MB in 3.01 seconds = 178.93 MB/sec kayano:/ # dd if=/dev/zero of=/tmp/test bs=1024k count=10240 10240+0 records in 10240+0 records out 10737418240 bytes (11 GB, 10 GiB) copied, 60.5833 s, 177 MB/s
読み書きともに 177MB/sec くらい。 Core i5-8500 マシンだとこんなもの? 同じ PC (同じ Windows) で VMware 上の同じ GCD OS でも測ってみると、 読込みが 180.62 MB/sec で書き込みが 132 MB/s だった。 簡易な測定なので、ほぼ同等と言っていいと思う。
ちなみに仮想化なしで直接このマシン上で測ると、 読込み 178.65 MB/sec 書込み 233 MB/s なので、 仮想マシンによるオーバーヘッドは多少あるようだ。 とはいえ AMD FX-4100 マシンとかだと読込み 174.82 MB/sec 書込み 95.3 MB/s なので、 実用上は 177MB/sec もあれば充分?
AMD FX とかの 10年前のマシンだと CPU がボトルネックになってる感じ。そろそろ 「Sandyで十分おじさん」 は卒業すべき?
ネットワークの速度も測ってみた:
kayano:/ # iperf3 -c esaka Connecting to host esaka, port 5201 [ 5] local 172.17.14.235 port 35504 connected to 192.168.18.20 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 116 MBytes 970 Mbits/sec 0 3.01 MBytes [ 5] 1.00-2.00 sec 111 MBytes 933 Mbits/sec 0 3.01 MBytes [ 5] 2.00-3.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 3.00-4.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 4.00-5.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 5.00-6.00 sec 111 MBytes 933 Mbits/sec 0 3.01 MBytes [ 5] 6.00-7.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 7.00-8.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 8.00-9.00 sec 112 MBytes 944 Mbits/sec 0 3.01 MBytes [ 5] 9.00-10.00 sec 111 MBytes 933 Mbits/sec 0 3.01 MBytes - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 1.10 GBytes 943 Mbits/sec 0 sender [ 5] 0.00-10.05 sec 1.10 GBytes 938 Mbits/sec receiver iperf Done.
1Gbits/sec の LAN なので、ほぼ上限の速度が出ている。 同条件で VMware でも測ってみると、 送信が 928 Mbits/sec で受信が 925 Mbits/sec だった。 仮想化なしでも、 送信が 943 Mbits/sec 受信が 941 Mbits/sec なのでほとんど同じ。
というわけで、 速度的にはディスクもネットワークも問題無さそう。
5/12 追記:
Windows を再起動すると、 仮想スイッチ 「WSL」 が 「外部ネットワーク」 に接続する際にエラーが発生し、 接続先が 「内部ネットワーク」 に戻ってしまう。 これは ネットワーク アダプター が既に使われているため。 起動直後は ネットワーク アダプター が 「Microsoft 仮想スイッチ プロトコル」 にバインドされているらしい。
したがって、 ネットワーク アダプター のプロパティを開いて、 全ての項目のチェックを外してから 「外部ネットワーク」 に切替えればよい。 と言っても、 毎回コントロールパネルを開いて、 ネットワークアダプタのプロパティを変更するのは現実的ではないので、 PowerShell スクリプトでネットワーク アダプター ($nic) のバインドを外すことにする:
$nic = "Realtek PCIe GbE Family Controller" Disable-NetAdapterBinding -InterfaceDescription $nic Set-VMSwitch -name WSL -NetAdapterInterfaceDescription $nic
「Disable-NetAdapterBinding」 でネットワークアダプターの全てのバインドを外し、 次の 「Set-VMSwitch」 で仮想スイッチ 「WSL」 の接続を、 このネットワークアダプターへ切替える。
これで仮想スイッチ 「WSL」 がネットにつながった。
これで仮想スイッチにつながっている WSL2 は通信できるようになる。 WSL2 から外部へのアクセスはもちろん、 外部から WSL2 へのアクセスも自由。
ところが肝心の Windows 自体がネットから切り離されたまま。 WSL2 は通信できるのに、 Windows の WSL2 以外のプログラムは通信できない、 という (ちょっと) 不思議な状態に陥ってしまう。
再起動したとき、 一時的に仮想スイッチが無くなるので、 Windows は仮想スイッチ経由でネットに接続できていたことを忘れてしまうみたい。
仮想スイッチ 「vEthernet (WSL)」 のプロパティを開いて、 「Microsoft ネットワーク用クライアント」 や 「インターネット プロトコル バージョン 4 (TCP/IPv4)」 などに再度チェックを入れれば通信できるようになる。
もちろん手作業ではやってられないので、 次の PowerShell スクリプトで行う:
Get-NetAdapterBinding -name 'vEthernet*' | ? ComponentID -Match 'vmware_bridge|ms_tcpip|ms_tcpip6|ms_server|ms_msclient' | Enable-NetAdapterBinding -PassThru
「Get-NetAdapterBinding -name 'vEthernet*'」 で仮想スイッチのバインドを列挙し、 「Microsoft ネットワーク用クライアント」 や 「インターネット プロトコル バージョン 4 (TCP/IPv4)」 など、 チェックを入れたい項目を抜き出して (ComponentID -Match)、 チェックを入れる (Enable-NetAdapterBinding)。
以上まとめると、 起動時に次の PowerShell スクリプト gcd.ps1 を実行すれば良い:
wsl --mount '\\.\PHYSICALDRIVE1' --bare wsl -u root -- /mnt/c/bin/gcd.sh $nic = "Realtek PCIe GbE Family Controller" Disable-NetAdapterBinding -InterfaceDescription $nic Set-VMSwitch -name WSL -NetAdapterInterfaceDescription $nic Get-NetAdapterBinding -name 'vEthernet*' | ? ComponentID -Match 'vmware_bridge|ms_tcpip|ms_tcpip6|ms_server|ms_msclient' | Enable-NetAdapterBinding -PassThru
Windows の起動時に C:\bin\gcd.ps1 を実行するには、 タスクスケジューラの 「操作」 において、 「プログラム」 として 「%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe」 を設定し、 「引数」 として 「C:\bin\gcd.ps1」 を設定すればよい。
「wsl --mount」 を PowerShell スクリプトへ移したことで、 C:\bin\gcd.sh は以下のようになる:
#!/bin/sh ROOT="/usr/local/GCD" test -d $ROOT || mkdir $ROOT mount LABEL=/ $ROOT $ROOT/etc/init.d/chroot
11/7 追記:
Windows 11 22H2 (ビルド 22621.674) になって、 物理ネットワーク アダプターがバインドされたままでも、 仮想スイッチ WSL につなぐことができるようになった。
したがって、 C:\bin\gcd.ps1 においてバインドを外す以下の部分が不要になる:
$nic = "Realtek PCIe GbE Family Controller" Disable-NetAdapterBinding -InterfaceDescription $nic
もちろん、外したバインドを元に戻す以下の部分も不要になる:
Get-NetAdapterBinding -name 'vEthernet*' | ? ComponentID -Match 'vmware_bridge|ms_tcpip|ms_tcpip6|ms_server|ms_msclient' | Enable-NetAdapterBinding -PassThru
さらに、 GCD OS の中で Set-VMSwitch を実行すれば C:\bin\gcd.ps1 が不要になる。 具体的には /etc/rc.d/rc.local で以下を実行するようにした:
cd /mnt/c Windows/System32/WindowsPowerShell/v1.0/powershell.exe <<EOF Set-VMSwitch -name WSL -NetAdapterInterfaceDescription "Realtek PCIe GbE Family Controller" EOF
これで冒頭のシェルスクリプト C:\bin\gcd.sh だけで済む。