Windows Subsystem for Linux 2のメモリ管理を詳しく見る

文●塩田紳二 編集● ASCII

2019年11月24日 10時00分

現在プレビュー中の20H1のビルド19013から、WSL2(Windows Subsystem for Linux 2)は、一旦確保したメモリでも不要になれば、きちんとWin32に返すようになった。今回はこのあたりを調べてみる。

プレビュー中の20H1に含まれるWSL2では、不要なメモリをWin32側に返すことができるようになった。グラフは、WSL2側でメモリを消費させ、その後開放したときの様子である

WSL2におけるメモリ割り当て

WSL2は、軽量ユーティリティ仮想マシン(Light Weight Utility Virtual Machine:以下、LWUVM)内で動作している。このため、ホストとなるWindows 10のメモリの一部を利用する。ただし、メモリ割り当ては、Hyper-Vでいう動的メモリ割り当てであり、上限を決めるものの、実際に使っている分のみを確保して、必要になれば上限までメモリを確保しようとする。

この上限は、WSL2側からは「実装メモリ量」のように見える。デフォルトでは、PC側のメモリの75%をWSL2の上限として割り当てているようだ。たとえば、物理メモリ4GBのマシンならば3GBが、8GBのマシンなら6GBが実装メモリとしてWSL2内からは見えている。

またWSL2では、Linuxのカーネルがそのまま動作しており、仮想記憶機構が動作している。Linuxでは、実装メモリ以上のメモリを使えるようにswapを設定できる。Linuxのswapには、ファイルもしくは、パーティションを割り当てることができるが、どうもWSL2では、デフォルトで「/swap/file」という仮想的なswapファイルが設定されているようだ。

“仮想的”としているのは、実際にはこうしたパスを持つファイルやデバイスはなく、WSL2側からは何も見えないからだ。しかしWSL2の実装メモリを越えそうになると、swapが使われるようになる。なお、仮想swapファイルは、Hyper-Vなどで利用する仮想HDDファイル(vhdxファイル)になっており、「%localappdata%\Temp\swap.vhdx」が使われるようだ。このswapファイルに関しては別途解説する予定だ。

メモリやswapは、WSL2をホストしているLWUVMが実現しているもので、WSL2では、その制御をユーザーフォルダーにある「.wslconfig」ファイルで指定できるようになっている。このファイルは単純なテキストファイルで1行ごとにキーワードを書いて設定する。まとまった資料がないようなので、とりあえず判明している範囲のキーワードを以下の表に示す。なお、このファイルによる指定には、すべてデフォルト値を持っているため、ファイルの中身は空、あるいはファイル自体がなくてもWSL2の動作には影響しない。

実装メモリ量を指定してみる

まずは、.wslconfigで実装メモリ量を変更してみよう。ただし、.wslconfigの設定を反映させるには、一回WSL2をシャットダウンして再起動させる必要がある(Linuxの再起動ではない点に注意)。それには、以下のコマンドなどで、WSL2を終了させる。

wsl.exe --shutdown

その後、.wslconfigを書き換えてWSL2のディストリビューションを起動する。これでWSL2側が認識する実装メモリ量が増える。

メモリ8GBのマシンで、WSL2を起動すると、デフォルトのメモリ割り当て量は6GB程度。%userprofile%\.wslconfigで「memory=4GB」を指定して起動すると割り当ては3.9GB程度になる

ただし、起動直後でbashのコマンド入力待ちの状態では、WSL2内では、50~60MB程度、Win32側で見ても270MB程度しかメモリを占有していない。

なお、WSL2内でメモリ量を調べるには、

free -h

とする。“-h”は数値を人間が見やすい形式にするオプションである。

メモリを大量に使い、その後に戻してみる

では、次に、メモリを大量に確保したあとに開放して、Win32側にメモリがもどされるかどうかを確認してみる。テストには、Ubuntu-18.04を用いた。メモリを消費させる方法にはいくつかあるが、ここでは、stressというコマンドをインストールして使う。インストール方法は、WSL2のコマンドラインから以下のコマンドを使う(Ubuntuの場合)。

sudo apt update
sudo apt install stress

実際にインストールするのは2行目のコマンドである。1行目はやる必要がないとわかっているならやらなくてもよい。蛇足ながら1行目は、その意味がわからない人がエラーを起こさないように入れてある。これでstressコマンドが利用できるようになる。このコマンドはシステムに負荷を掛けてテストをするためのものである。たとえば、メモリを1GB確保させたいなら、

stress --vm 1 --vm-bytes 1G --vm-keep

とする。コマンドラインの意味としては、プロセス1つ(--vm 1)に1GBのメモリを確保(--vm-bytes 1G)して、開放せずにそのままにする(--vm-keep)という意味になる。

詳しくはmanコマンドなどを参照されたい。このコマンドを起動すると、プロセスを強制終了するまで、1GBのメモリが確保されたままになる。なお、プロセスを終了させるにはkillコマンドを使う。

今回は、6GBを実装メモリとして認識しているWSL2に対して、4GBを確保してみる。stressコマンドでメモリを確保し、WSL2側、Win32側からメモリ利用量を測定したのが、以下の画面である。

WSL2内でstressによりメモリを消費させると、Win32側のvmmemプロセスがメモリを確保するのをtasklistコマンドで見ることができるほか、タスクマネージャーのメモリ利用量が増える。また、WSL2内でも、freeコマンドでusedになるメモリ量が増える

コマンド「stress --vm 4 --vm-bytes 1G --vm-keep &」として、4GBを確保すると、WSL2のfreeコマンドのusedの値が上がり、Win32のtasklistコマンドでvmmemのメモリ使用量が増大する。あわせて、タスクマネージャーのメモリ利用量が増える。

その後、バックグラウンドで起動したstressコマンドをkillコマンドで停止させると、メモリが開放され、タスクマネージャーを見ると、Win32側でもメモリ利用量が減るのがわかる。

stressコマンドをkillコマンドで終了させると、メモリが開放され、Win32側のvmmemがメモリを開放し、タスクマネージャーのグラフも元に戻る

このように、WSL2は一時的にメモリを大量に確保しても、そのあとWin32側にちゃんと戻すことができるようなので、長時間のWSL2利用も問題はなさそうだ。もっとも、これまでメモリを戻すことができなかったのは開発途上だったからだ。

Win32側では、WSL2のメモリイメージは、vmmemというプロセスとして見える。つまりWSL2側でメモリを使うとき、vmmemがWin32側でメモリを確保していく。WSL2でメモリが開放されると、vmmemが確保していたメモリ領域を開放する。

この仕組みは、Linux側のメモリコンパクション(メモリ圧縮)とLWUVMの協調動作として実現される。Linux側では、不要なメモリ領域を集めて連続するページを作るメモリコンパクション機能がある。このメモリコンパクションは、分断されたページの中で、隣合う空きページをつなげて大きな領域とし、その後、移動可能な利用中のページと空きページを入れ替えながら、連続する空きページの領域を大きくしていく。

Linux内では仮想記憶が動作しているが、DMAを使うネットワークのバッファなどは、物理的に連続している必要がある。また、小さなページを大量に使うと、仮想アドレスを高速に物理アドレスに変換するTLB(Translation Lookaside Buffer)を大量に消費し、効率が悪くなる。Linuxでは、連続する複数の4Kページをまとめて1つのHuge Pageとして管理し、1つのTLBエントリーで複数ページをまとめて示せるようになっている。そのため、空き領域を連続して確保できるようになっている必要があり、メモリコンパクションをする。

本来は、メモリ領域の分割状態などを見てメモリコンパクションを起動するが、WSL2では、アイドル状態が一定以上続いたら、強制的にメモリコンパクションを起動しているようだ。

なお、Linuxでは、rootユーザーで/proc/sys/vm/compact_memoryに「1」を書き込むと強制的にメモリコンパクション処理が実行される(これはLinuxの標準の機能)。この動作に関しては、前述の.wslconfigで設定が可能だ。

“pageReporting”キーワードは、空きページが存在することをLWUVMのハイパーバイザー(Hyper-V)側に通知する機能のオンオフで、これをtureとすることで、WSL2は空きページの集まりをHyper-Vに通知して、Win32側に返却させることができる。“idleThreshold”は、前述のメモリコンパクションを起動するアイドル時間の閾値で、デフォルトは1秒であり、この値を0にすると、メモリ圧縮をアイドル時に実行しなくなる。

このメモリコンパクションの動作は、カーネルのメッセージを表示するdmesgで確認できる。強制的にメモリコンパクションが起動すると「WSL2: Performing momory compaction.」というメッセージが記録される。

WSL2側で強制的に起動されるメモリコンパクションは、dmesgでメッセージとして見ることができる

■関連記事