WindowsとUbuntuの相互運用の仕組み(前編)
「Build 14951」にて、「Bash」からWindowsバイナリーを直接起動できるようになりました。初めに
Win32アプリケーションとの相互運用は、WSLに求められていた機能の中で上位に位置する機能だった。WSLをWindows Insider向けに提供していた当初から、我々のユーザーは極めて重容な問題を指摘し、WSLを利用することで活用したいシナリオを提案してきた。
WSLを積極的に利用し、積極的にフィードバックを提供してくれたユーザーがたくさんいたことは、我々にとって光栄なことであり満足の行く体験であった。
我々はすべての不具合報告と提案に対し感謝している。
不具合報告と提案は、Windowsの将来のバージョンで実装する機能の優先順位付けを行うため、非常に有益な情報である。
Win32アプリケーションとの相互運用機能の要望が上位に現れるまで、長い時間はかからなかった。
相互運用とは何か
まず初めに、相互運用とは何を指すのかを定義しよう。相互運用とは広義の意味では、同一のシェル内でNTバイナリーとLinuxバイナリーを混在させ、両バイナリーを組み合わせて利用できる性質のことである。
例えば、「bash.exe」を利用してファイルシステムを操作し、NTのGUIテキストエディターを現在の作業ディレクトリーから起動する。
例えば、NTバイナリーとLinuxバイナリーの入出力をリダイレクトする。
例えば、Windowsコマンドの出力をLinuxのgrepバイナリーでフィルタリングする。
このような以前のWSLでは不可能だったことができるようになったのだ。
あなたが利用しているシェルからお気に入りのネイティブのWindowsやLinuxツールを分け隔てなく動作させることができるのだ。
WSLプロセスの起動
プロセスを起動した時にいくつか共有しなければならない情報がある。ひとつは現在の作業ディレクトリー(current working directory)である。
もうひとつは、標準入力、標準出力、そして標準エラーを表すファイルオブジェクトである。
これらのファイルオブジェクトは、新しいプロセスから正しい場に向けて情報を出力するために必要である。
例えば、もう一つのプロセスへパイプ出力を行い出力をファイルへリダイレクトしたり、
意図したコンソール環境で確実に出力内容を表示できるようにするのに必要である。
どのように情報の共有を行うのか
以下の図は、必要な情報の共有をどのように行うのかを説明した図である。/bin/bashの起動
1.COM
「bash.exe」は「LXSS Manager service」と通信するため、内部のCreateLxProcess COM APIを使用する。2.ioctrl
「LXSS Manager service」は現在の作業ディレクトリーをWSLのパスに変換する。また「bash.exe」によって提供される標準入力、標準出力、標準エラーのNTハンドルとコンソールハンドルを「LxCore.sys」で「マーシャリング」する。
3.LxBus Msg
「LXSS Manager service」は我々が提供するカスタマイズされた「/init」デーモンへ「LxBus」を通じてメッセージを送る。4.ioctrl
「/init」デーモンは送られてきたメッセージを解釈し、標準入力、標準出力、標準エラー、コンソールをアンマーシャリングする。標準入力のファイル記述子を「0」にセットし、標準出力のファイル記述子を「1」にセット、そして標準エラーのファイル記述子を「3」にセットする。
さらにttyのファイルディスクリプターを作成する。
5.fork / exec
「/init」はフォークを行い、「/bin/bash」バイナリーを実行する。そして「/init」は次のプロセス生成メッセージの受信に備える。
これで「/bin/bash」の起動が完了する。
WSLインスタンス内からNTバイナリーの起動
6.fork / exec
「/bin/bash」はフォークを行い、NTバイナリーを実行する。「LxCore.sys」は「binfmt_misc」からWindows PEイメージを扱うインタープリターの情報を探し、見つかったインタープリター(/init)を呼び出す。
7.ioctl
「binfmt_misc」のインタープリターとして実行されている「/init」は、現在の作業ディレクトリーを変換し、ファイルディスクリプター0(標準入力),1(標準出力),2(標準エラー)を「LxCore.sys」でマーシャリングする。8.LxBus Msg
「/init」は「bash.exe」にNTプロセス生成メッセージを送る。9.ワーカースレッド
「bash.exe」はファイルディスクリプターをアンマーシャリングし、NTハンドルを作成する。標準入力、標準出力、標準エラーを表すNTハンドルを指定し「CreateProcess」APIを呼び出す。
これで「NTプロセス」 が起動する。
LxBusとは
「LxBus」は、NTプロセスとWSLプロセス間で使われるコミュニケーションチャンネルである。主に「LXSS Manager service」と「/init」間の通信に使われる。
「LxBus」は、NTプロセスとWSLプロセス間でメッセージを送受信したり状態を共有するプロトコルのようなソケットである。
WSLインスタンス内のLxBus
WSLインスタンス内では、「LxBus」へのアクセスは「/dev/lxss」デバイスを開くことで「LxBus」にアクセスする。「/dev/lxss」デバイスは、「/init」デーモンにより管理されている。
また「/dev/lxss」デバイスの機能のサブセット(一部分)をサポートする「/dev/lxssclient」デバイスも存在する。
WindowsからLxBusへのアクセス
「Windows」から「LxBus」へのアクセスは、既存のWSLインスタンスを表すハンドルを経由して「LxBus」へアクセスする。様々なioctlが利用可能に
上記デバイスのファイルディスクリプター(WSL)やハンドル(NT)を取得すれば、様々な「ioctl」が利用できるようになる。利用可能な「ioctl」の中で本件の相互運用に最も関係性のある機能が、「Register Server」と「Connect to Server」である。
Register Server ioctl
外部から接続を受け入れるNTプロセスやWSLプロセスは、「LxCore.sys」ドライバーに「Register Server ioctl」を使用して名前付きの「LxBusサーバー」を登録する。登録が完了すると、「LxBusサーバー」を表すファイルディスクリプターやハンドルが呼び出す側に返される。
このファイルディスクリプターやハンドルのことを、「LxBusサーバーポート」と呼ぶ。
「Register Server ioctl」はソケットの生成と接続だと考えてもらえば良い。
一旦「LxBusサーバー」が登録されれば、その「LxBusサーバーポート」オブジェクトは他プロセスからの接続を待ち受けるソケットような「Wait for Connection ioctl」を利用できるようになる。
Connect to Server ioctl
クライアントが登録された「LxBusサーバー」に接続するには、「Connect to Server ioctl」を使用し「LxBusサーバー」に接続する。この時この「ioctl」に接続したい「LxBusサーバー」の名称を指定する。
「LxBusサーバー」が接続を待っている状態なら、NTプロセスとWSLプロセス間で接続が確立される。
この時NTプロセスは接続を表すハンドルを受け取り、WSLプロセスは接続を表すファイルディスクリプターを受け取る。
LxBus MessagePort
これらのハンドルやファイルディスクリプターは「LxBus MessagePort」と呼ばれるファイルオブジェクトである。「LxBus MessagePort」は、メッセージを送受信するためにデータの読み書きをサポートしている。
また「LxBus MessagePort」は、様々な目的を完遂できるようにいくつかの「ioctl」を実装しており、その主な目的は「NTプロセス」と「WSLプロセス」のリソースの共有である。
例えばある「ioctl」は、「NTプロセス」に「WSLプロセス」とハンドルを共有する機能を提供する。
これを利用すれば「WSLプロセス」の出力をリダイレクトし、「NTプロセス」に渡すことができる。