WSL1とWSL2で処理を切り分けるには?そもそも何が違うの?


Windows Subsystem for Linux

Windows11の最大の特徴はWSLではないかと一部で言われるように妙に注目度の高いWSLですが、登場から既に5年以上が経過しています。当初Bash on Windowsという名称だったので、「ああ、Bash cloneがWindowsのオマケに入るのね」程度に完全に誤解していました。その後、Widndows Subsystem for Linux と名前を変えた際にも「POSIX Subsystemみたいなことをまたやるんだ」程度に思ったのは筆者だけではないはず。その後数年の時が流れWSL2でHyper-Vレイヤにカーネルを取り込むという話から興味をもってWSLの情報を再度見返したところ。コンソールウィンドウの中にLinuxの世界観が完全に再現されているではないですか。素晴らしい理想の環境じゃないかと感動するとともに、今までの無関心を大いに反省しました。

WSL1とWSL2って?

ところで、WSLにはWindowsの中にLinux互換システムコールを組み込んだWSL1、そしてLinuxカーネルをハイパーバイザー上で起動させるWSL2が存在します。この二つはversion1,2 という名前が悪く、WSL2は改良された新しいバージョンで、WSL1は古いバージョンという見方をされてしまいますが実は、コンセプトの異なる別のアプローチです。

WSL1は90年代にも既に存在していた同一アーキテクチャのコンピュータ上で他のOSをシステムコールレベルで再現するものです。従って、ベースはあくまでもWindowsでそこにLinux互換のシステムコールを用意して、上位のアプリケーションを動作させるプラットフォームを提供するものです。

対して、WSL2はハードウェアエミュレーションをさらに効率を高めた今どきの仮想化技術であるHyper-Vの元に 、Linuxカーネルを起動させることに特化することで起動速度を速めた仮想マシンです。

おおよそのことはWSL1で事足りるのですが、互換システムコール開発が追従できていない(=実現が難しい or 優先度が低い)と動作しません。またアプリケーションの表層では同じに見えても下層では処理が異なるので出来ることや挙動が異なる場合もあります。従って、この二つは新旧というよりも目的によって使い分けるものだと思います。

大勢の考えがWSL2 = 新バージョンなのでWindows11でもデフォルトのバージョンがWSL2になってしまっていますが、どちらがデフォルトかというよりもインストール時に選ばせてほしいですね。後から変えられるので困るわけではないですが。

どういうときに違いがあるのか?

違いを認識している人には釈迦に説法なので説明は不要ですが、漠然と疑問を持つ場合には大勢に従ってWSL2を使えば良いと思います。違いに遭遇するまで気にしなくて良いでしょうし、両方インストールすれば良いだけなのでさほど悩むこともありません。

一つの例を挙げると、sshdを起動して他のPCからアクセスできるようにする場合、この両者には明確な違いが出ます。

WSL1のネットワークスタックはWindowsのものが使われますです。従って、他のPCからはホストであるWindowsと同じIPアドレスで見えます。対して、WSL2のネットワークスタックは仮想マシンのLinuxカーネル内です。こちらは仮想ネットワークに接 続されるので、Windowsホストの外から直接アクセスできません。

このことから、同じsshdを起動してアクセスできるようにするにしても手順が異なります。

WSL1 では
- sshdを起動する
- Windows Firewallで22/tcpにアクセス可能にする

WSL2 ではこれに加えて
- WSL2インスタンスの22/tcpへWindowsホストの22/tcpをポートフォワードする

こういう差異を含めて1本のシェルスクリプトで管理するには今どちらのWSLなのかを認識する必要があります。

どうやってみわける?

見分け方には幾つかあるのですが、一つは環境変数WSL_INTEROPを使って見分けられます。
WSL2ではWindowsとの相互運用のためのソケットが環境変数に設定されているのでこれで見分けがつきます。し かし、sudoした際などに環境変数が引き継がれず見失う場合があるので確実ではありません。

ではどうすれば?WSL1はWindowsの互換機能を使ってシステムコールを処理するのでLinuxカーネルが起動しません。従って、カーネルに付随する情報が幾つか無い(必要ない?)のでこれを見て判断できます。具体的には /proc 以下を覗くとWSL1の場合にはWSL2等Linuxカーネルが起動している場合に比べてファイルが少なくなっていますからこの中の一つを使って判断します。

getWSLversion () {
    if [ -f /proc/kmsg ]; then
        echo 2
    else
        echo 1
    fi
}

このような共通関数を一つ作っておくと処理の途中でWSL1, あるいは2に特化した処理を挟み込むのが完結にな ります。
実際の切り分け部分はこういうかんじですね。

if [ $(getWSLversion) -eq 2 ]; then
    ....
fi