このページの本文へ

Windows Info 第83回

Windows Subsystem for LinuxにおけるWin32相互運用性のメカニズム

2017年02月05日 10時00分更新

文● 塩田紳二 編集● ASCII.jp

  • この記事をはてなブックマークに追加
  • 本文印刷

 今回は、前回紹介したWin32相互運用性を実現している仕組みを見ていくことにしよう。驚いたことにこの機能は、Windows Subsystem for Linux(WSL)内にインストールされたLinuxをほとんど変更することなく実現されている。

 本文に入る前に1つ訂正させていただきたい。前回の最後でbash.exeがパイプを受け付けないとしていたが、現在のインサイダープレビューでは、正しく動作する。つまり、

dir | bash -c "grep test"

として、Windows側のdirコマンドの出力をLinuxのgrepで処理することができる。

Windows側からもLinuxのコマンドをbash.exeを介することで起動することができる

Win32アプリかLinuxの実行ファイルかを区別する仕組み

 まず、bash側でWin32アプリを判別して起動する仕組みが必要になる。しかし、WSL内では標準のUbuntuがそのまま動作しており、/bin/bashを改良するわけにはいかない。そのため、Linuxカーネル標準の機能である「binfmt_misc」を使う。

 これは、ファイルのフォーマットなどからデータファイルを識別して、これを解釈実行する「インタプリタ」を起動する仕組みだ。Windowsでいえば、拡張子と実行ファイルの「関連付け」に似た機能だ。

 このbinfmt_miscでは、ファイルの先頭に埋め込まれた「マジックナンバー」を識別に使うことができる。EXE形式のプログラムは先頭に必ず「MZ」という文字列がマジックナンバーとして埋め込まれている。これは、MS-DOSの頃からの定義で、当時は、同じ実行ファイル形式だったCOM形式との区別に使われていた。

 その後、Windowsが登場してWindowsアプリケーションとなったが、構造的には、EXE形式のプログラムが先頭部分にあり、これを実行したあと、Windowsかどうかを判断して、その後に置かれたWin32バイナリを起動する仕組みになっていた。

 このような構造になっているのは、DOSから起動したときに、Windows用であるというメッセージを表示させて停止するためだ。しかし、ウィルスの侵入路に使われてからは、先頭にあるDOSのEXE実行形式部分はまったく使われなくなった。しかし、形式としてはそのまま残っているのだ。

 Linuxの実行ファイル形式ELFは、Windowsのものは違うため、マジックナンバーもまた違ったものになっている。このため、標準のLinux実行ファイルとWindowsの実行ファイルは確実に区別ができる。

 WSLでは、Linuxカーネルは存在せず、WSL側にあるLXSS.SYSとLXCORE.SYSがカーネルAPIコールを受け付け、WindowsカーネルAPIに変換している。このため、WSLの実装自体がカーネルの機能に相当する。実は、RS1の実装ではこのbinfmt_miscは実装されていなかった。

 この機能があるかどうかは、Linuxの/procファイルシステムで判定できる。RS1には「/proc/sys/」以下にfsディレクトリが存在していないが、RS2のWSLでは、「/proc/sys/fs/binfmt_misc」ディレクトリが存在する。

RS1(Windows10 バージョン1607)のWSLには、/proc/sysの下にfsがない

RS2プレビュー版では、/proc/sys/fs/WSLInteropが存在する

 つまり、この機能は、RS1では未実装だったが、RS2では実装されているというわけだ。

 RS2のbinfmt_miscには、標準で以下の画面のようなEXEファイル実行用の登録がある。

/proc/sys/fs/WSLInteropには、インタプリタとして/initが、マジックナンバーとして先頭(offset 0)にMZ(0x4d5a)が指定されている

interpreter /init
flags:
offset 0
magic 4d5a

 これは、対象ファイルを判定するのにファイル先頭のマジックナンバーを使い、そのパターンは16進数で「4d5a」、ASCIIコードの「MZ」で、ファイルを解釈するインタプリタは「/init」だという定義だ。

 WSL内の/initは、通常のLinuxプログラムではなく、WSL専用の改良されたプログラムになっている。Linuxでは/initは、システムの起動時に最初に動作するプログラムで、環境整備などを行ったのち、bashを起動している。

 このプログラムは、WSLではWindows側と通信する唯一のプログラムであり、唯一の標準でないLinuxのプログラムだ。WSLの/initは、通常、LxCore.sys、LxSS.sysと通信し、Windows側のLXSSマネージャーサービスを経由してbash.exeに結果を返す。

 RS2では、WSL内で/bin/bashがEXEプログラムを起動すると、通常のコマンドライン処理(シェル変数の処理など)が行なわれたのち、LinuxのカーネルAPIコールが呼び出される。ここでbinfmt_miscが適用され、インタプリタとして/initが起動する。/initではファイルパスや標準入出力の変換が行われ、LxBUS経由でBash.exeにプロセス起動のメッセージが送られる。

 なお、binfmt_miscの登録にあるように、WSL内のLinuxでは/init自体がインタプリタとして動作し、コマンドラインで引数としてEXEファイルを指定すれば、Win32実行ファイルを起動できる。ただし、このときの起動パスは、DrvFSで正しく指定する必要がある。

/initの引数としてWin32バイナリを指定すれば、起動が可能。ただし、DrvFSのパスを指定する必要がある

 また、/initは、WSLが起動されたときのWindows側のPath環境変数をWSL側の/bin/bashへ持ち込む。もちろん、DrvFSに変換したうえでだ。これにより、WSL内ではWindows側のPathが初期値となり、これにWSL側のPATHが追加される。

 /initは、引数で指定されたWin32バイナリをLxCore、LxSSに依頼して起動させる。

「Bash.exeがLXSS Manager service経由でWSLを起動を要求」→「LXSS Manager Serviceは、標準入出力をLxBusに登録(マーシャリング)してWSLを起動」→「/initが起動し受け取った識別子からファイルディスクリプタを作成(アンマーシャリング)」→「/initがアンマーシャリングしたファイルディスクリプタを標準入出力に割り当て/bin/bashを起動」→「/bin/bashがbinfmt_miscで/initを起動。/initは、その時点の標準入出力をマーシャリングしてLxBusに登録。LxBus経由でbash.exeにWin32バイナリの起動依頼メッセージを送る」→「Bash.exeはメッセージを受け取り、識別子をアンマーシャリングして標準入出力のファイルハンドルを受け取る」→「受け取ったファイルハンドルを標準入出力に割り当ててWin32バイナリを起動」

 こうした通信には、LxBusと呼ばれるメカニズムが使われている。これはWSL側では、/dev/lxssと/dev/lxssclientというデバイスで、/initがこれを占有して使っている。

LxBusのため、WSL側には/dev/lxssと/dev/lxssclientというキャラクタデバイスが登録されている

 LxBusは、WindowsとWSLの間の複数の並行した通信を可能にする。1つは、bash.exeの背後にある「LXSS Manager Service」と/init間でメッセージ(たとえば、WSLの起動やWSL側からのWin32バイナリの起動依頼などのメッセージ)のやりとりを行なう。

 もう1つは、Windows側とWSL側の標準入出力を接続するための通信路として使われる。このときには、Windows側ではファイルハンドル、Linux側はファイルディスクリプタとして動作する。つまり、どちら側も、ファイルのように扱えるオブジェクトとして振る舞うことができる。なお、これはLxBus MessagePortと呼ばれている。それぞれの環境での汎用ファイルオブジェクトなので、標準入出力に割り当てることができる。これにより、Win32相互運用性では、Windows側とWSL側の標準入出力を接続することが可能になる。

カテゴリートップへ

この連載の記事

注目ニュース

ASCII倶楽部

プレミアムPC試用レポート

ピックアップ

ASCII.jp RSS2.0 配信中

ASCII.jpメール デジタルMac/iPodマガジン