システムコールとは(後編)
前回LinuxカーネルのシステムコールとWindows NTカーネルのシステムコールを紹介しました。これらのシステムコールの違いは、ABI周りの僅かな違いでした。
それらを踏まえて「WSL」のシステムコールを紹介します。
「WSL」は、LinuxのELF64バイナリーをそのまま実行するためのサブシステムです。
ですので、「WSL」のシステムコールの呼び出し規約は、Linuxカーネルのシステムコールの呼び出し規約に従います。
「WSL」はカーネルモードで動作するピコドライバー(lxss.sysとlxcore.sys)を含んでおり、ピコドライバーはLinuxのシステムコールをユーザーモードのソフトウェアに提供し、LinuxのシステムコールをWindows NTカーネルと協調して実行します。
ピコドライバーにはLinuxカーネルから拝借したコードは含まれておらず、クリーンルーム方式でLinuxカーネルと互換性のあるインターフェースを実装しています。
1.syscall命令の実行
ユーザーモードで動作しているLinuxのソフトウェアが「syscall」命令を実行すると、Windows NTカーネルはプロセス構造をチェックすることでピコプロセスからきた要求を検出します。Windows NTカーネルはLinuxのシステムコールを実行(解釈)できないため、ユーザーモードのスレッドのレジスターの内容を保存した後、ピコドライバーに処理を依頼します。
2.ピコドライバーのシステムコールの準備
Windows NTカーネルからシステムコールの処理を依頼されたピコドライバーは、まずどのシステムコールを呼ぼうとしているのか「rax」レジスターを確認し判別します。システムコールの判別を行ったら、Linuxの呼び出し規約に従いレジスターからシステムコールのパラメーターを取得します。
3.システムコールの実行
システムコールを実行する準備が整ったので、ピコドライバーはシステムコールを実行します。この時必要に応じてWindows NTカーネルの機能を呼び出します。
4.処理結果を渡す
システムコールの実行結果をWindows NTカーネルに渡します。Windows NTカーネルは、「1.」で保存したレジスターの内容でコンテキストを元に戻します。
そしてシステムコールの実行結果を「rax」レジスターに保存します。
5.ユーザーモードに戻す
システムコール命令の実行でカーネルモードへ移行しました。Windows NTカーネルは特別な命令(大抵は「sysret」か「iretq」)を実行し、ユーザーモードに戻します。
というわけで
Windows NTカーネルのシステムコールの処理の流れと「WSL」のシステムコールの処理の流れを比較すると、「WSL」ではシステムコールの実行をピコドライバーが代わりに担っている(解釈・翻訳している)という違いがあります。WSLにおけるシステムコールの例
WSLにおけるシステムコールの例を見てみます。sched_yieldシステムコールの例
Linuxの「sched_yield」システムコールは、Windows NTカーネルが持っているシステムコールと1対1で対応できるシステムコールです。Linuxの「sched_yield」システムコールに対応するWindows NTカーネルのシステムコールは、「ZwYieldExecution」です。
「WSL」上で動作しているLinuxのプログラムが「sched_yield」システムコールを呼ぶと、Windows NTカーネルはピコドライバーである「lxss.sys」に処理を依頼します。
「lxss.sys」は、Windows NTカーネルのシステムコールである「ZwYieldExecution」を呼べば「sched_yield」システムコールの要求を実現できることを知っているため、「ZwYieldExecution」を呼び出します。
これでシステムコールの処理が完了します。
これは簡潔な例
「sched_yield」システムコールは、1対1で対応できるWindows NTカーネルのシステムコールが存在しているため、非常に簡潔に処理できるシステムコールです。しかしすべてのLinuxのシステムコールが、このように簡単に処理できるとは限りません。
例え似たような機能を持つシステムコールがWindows NTカーネルに存在していたとしても、それがLinuxのシステムコールの要求を実現できる機能を十分に提供していない場合、上記のように簡潔に処理することはできません。
例えばパイプやフォークがこれにあたります。
パイプ
Linuxのプログラムでは、データの受け渡しなど様々な場面でよく利用されるパイプという機能があります。Windowsにもパイプという機能があり、Linux同様にパイプを利用することができます。
機能的に似ていますが、仕組みは異なっています。
そのためLinuxのソフトウェアがパイプを利用する時に、「WSL」はWindows NTが提供するパイプ機能をそのまま利用することができません。
Linuxのパイプ機能が提供するすべての機能を、Windows NTが提供するパイプ機能では実現できないためです。
WSLがLinuxのパイプ機能を直接実装
そこで「WSL」は、Linuxのパイプ機能を直接実装しています。とはいえLinuxのパイプ機能を「WSL」だけで実現しているのではなく、データ構造や同期機能のようなWindows NTカーネルが提供している機能と組み合わせて、Linuxのパイプ機能を実現しています。
フォーク
「fork」システムコールも同様に、1対1での対応というわけには行きません。Linuxのソフトウェアが「fork」システムコールを呼び出した時、「lxss.sys」はプロセスをコピーするための準備を行います。
準備が完了したら「lxss.sys」はWindows NTの内部APIを呼び出し、プロセスを作成します。
加えて、プロセス内にスレッドを作成し同じ内容のレジスターコンテキストを用意します。
「lxss.sys」はプロセスのコピーを完了するためにいくつかの追加処理を行い、新しく作成したプロセスを実行可能な状態にします。