WSLは現在複数のバージョンがあり、利用形態によって大きく変わる場合があります。
ここでは Windows11 + ストア版WSL Preview 0.51.2.0 を基本に話を進めます。
WSL2のバージョン?
WSLには大きくWSL1, WSL2があります。WSL1はシステムコールレベルでのエミュレーションで、基本的にはWindowsカーネルがLinuxシステムコールを処理するものです。WSL2はハイーパーバイザー上でLinuxカーネルを動かしWindowsとシームレスに連携できるように工夫された仮想マシンです。これがよく言われるWSLのバージョンです。
さらに、WSL2は発展途上のため変更が多いのと、取り残されたバージョン、先に進んだバージョンがあり複雑です。最新のWindowsを使っていたとしても以下3パターンがあります。
Windows10 21H2
Windows11 21H2
Windows11 21H2 + ストア版WSL Preview
従って、バージョンによってはここに書かれた挙動とは違うこともあるとご留意ください。
/init
WSL2ではPID1で起動するinitプロセスを/initという独自のプログラムに置き換えています。最近のLinuxディストロではsystemdが使われていることが多く、ここに不満を持たれている方も多いようです。initの役割はunixシステムで最初に起動して他のプロセスを起動する役割ですが、WSL2の /init はWindowsコマンドの実行を可能にする連携機能やVMへの接続、VMのシャットダウントリガー等いくつかの拡張機能を持つようです。
おそらくWSL2を構成する一番キモの部分になると思うのですが、ここはオープンソースではありませんし仕様も公開されていないようなので謎が多い部分です。Ubuntuがsystemdを採用する計画もあるようですが、単純な置き換えでなくWSL2 init の機能をデーモン化して持たせるような工夫がほしいですね。
さて、実働するWSL2でプロセスリストを見るとやたらinitが起動しているのが気になります。
$ pstree
init-+-dockerd-+-containerd---20*[{containerd}]
| `-22*[{dockerd}]
|-init---init---bash-+-more
| `-pstree
|-init---sshd
`-3*[{init}]
1行目のinitがPID1のinitです。直下にdockerdが起動しているのは、/etc/wsl.conf で起動させたものです。/etc/wsl.confで起動時実行のコマンドに指定すると、PID1のinitが子プロセスとして各コマンドを起動します。これは通常の起動プ ロセスと変わらないと思います。
ところが、Windows Terminal等からWSLコマンドで(?)接続するとinitがforkされるようです。通常はinitから起動されたloginプロセスやsshdなどのデーモンが対応すると思うのですが、loginの手続きもありませんから少し流儀が異なるようです。そして、forkされたinitプロセスからもう一つinitがforkされ、その子プロセスとしてシェルが起動しています。
このシェルの親プロセスにあたるinitがWindowsとの連携を担うようです。
シェルからsshdのような常駐プロセスを起動させた場合、シェルを終了させると親のinitとともに常駐プロセスが居残るようです。
-init---init---bash
---sshd
↓ ↓ ↓
- init---sshd
VMのアイドル状態
2020.04.29追記
WSL 0.58.3にて以前書いた以下の内容とは挙動が変わっている気がしたので検証してみました。
0.58.3 | 0.51.2 | |
---|---|---|
ターミナル接続時 | タイムアウトしない | タイムアウトしない |
切断時(シェルから常駐) | タイムアウトする | タイムアウトしない |
切断時(wsl.confから常駐) | タイムアウトする | タイムアウトする |
切断時インスタンス終了時間 | 15秒 | 75秒 |
切断時VM停止時間 | 75秒 | 75秒 |
大きくは二つです。以前はログインしてシェルからdaemonを起動しておくと、ターミナルを切断していてもWSL2インスタンスが停止することはありませんでした。ところが、現在は15秒で停止※してしまいます。そして、それから60秒後にVMが停止します。vmIdleTimeoutの60秒と合致しました。ということはvmIdleTimeoutのIdleというのは全てのWSL2インスタンスが停止している状態のこと?
※この状態はカーネルは起動したままで、Linuxコンテナが終了している状態です。再度ターミナルからシェルアクセスすると常駐していたプロセスがいなくなりPIDが若返っています。ただし、dmesgで見るとカーネル自体は以前のままです。
なんとなくvmIdleTimeoutは仕様通りになった気がしますが、ターミナルを切断して15秒でインスタンスが終了してしまうのは短すぎるきがするのでこちらはバグのような気がします。いずれにせよまだまだ未完成とということですね。
.wslconfigにvmIdleTimeoutという設定項目があります。これはWindows11だけで使えるオプションですが、VMがアイドル状態になってからシャットダウンするまでの時間を調整できるようになりました。デフォルトは60秒だそうです。
さて、このアイドル状態とはどいう状態でしょう?
- シェルを起動している状態。この時は無操作状態であったとしてもシャットダウンされることはありません(=非アイドル)。
- 先のようにsshdのような常駐プロセスを起動している場合はシェルを切断してもシャットダウンされることはありません(=非アイドル?)。
- wsl.confの起動時プロセスとしてdockerdを常駐させていてもすべてのシェルを切断すると75秒でシャットダウンされます(=アイドル?)。
シャットダウンされないケースの特徴を探すと、PIDが1でない(=シェルを起動させるための)initが起動している状態のようです。ただし、75秒という数字は先ほどの60秒とは異なりますね。
vmIdleTimeoutを30秒に設定すると45秒でシャットダウンが発生します。どうもvmIdleTimeout + 15秒でシャットダウンされるようです。
この15秒は?
- タイムアウトするとSIGTERMを投げ、15秒後にVMを終了する?
- initプロセスがいなくなった後15秒が経過するとアイドル状態と判定する?
アイドル状態になるとタイマーが働くので、その前に15秒監視タイマーを動かす意味はなさそうと考えると1でしょうか?
VMのシャットダウン?
先の検証でアイドルタイムアウトとVMのシャットダウンには15秒の隙間があるのですが、WSL.exe --shutdown だと15秒待つことなく、一瞬でVMが停止します。ちょっと話が合わないですね。
そこで、シグナルを受け取るとファイルに書き出す簡単なスクリプトを作って検証したところ、shutdown(アイドルタイムアウトでのシャットダウン含む)では特にプロセスにシグナルが送られている形跡はありません。つまり、シャットダウンというよりは電源断のイメージっぽいです。
wsl.confでのコマンド実行は後付けなのであくまで初期設定用ということでしょうか(サンプルがdockerdの起動になっているのがよくない?)。そもそものコンセプトだとプロセスを常駐させるとWSL2の終了を抑止してくれるのでプロセスが起動している間はシャットダウンが発生しませんから。
それでも、Windows自体のシャットダウンやWSL.exeで終了させたときには稼働中プロセスがいきなりVMごと停止させられてしまう仕様ってどうなんでしょう?若干危険な香りはしますが、いままで大きなトラブルにはなっていないのでファイルシステムのフラッシュくらいはされてるということでしょうか。
高速スタートアップが有効だとシャットダウンしてもWSL2インスタンスはサスペンドされてるだけだったりというのもそういうこの辺りが理由なのかもしれません。
VM起動時のコマンド実行
wsl.confに以下のような記述をするとVM起動時にコマンド実行されます(この機能は現在Windows11以降で有効のようです)。
[boot]
command = service docker start
このコマンドはバックグラウンド処理されるようです。
.bashrcで起動時設定を行う際にはシェルの起動が遅くならないようにバックグラウンドに回す工夫をしていましたが、そういうことは不要のようですね。