ファイル I/O の API とフィルタドライバのふかーい関係

こんばんは。cleng です。

前回 5/13 のポストから、約一ヶ月間が経ち、この一ヶ月間の間、いくつかの出来事があって、皆さんに紹介したいと思います。また前回のポストでは、次回はミニフィルタの話をすると予告しましたが、フィルタドライバの話をする前に、ファイル I/O の API の話を少しお話したほうがいいかもしれないと、思うようになりました。ミニフィルタの話は、また今度ということで、ご勘弁ください。

まず、コンサートの話です。マイクロソフトには社員が有志で作った管弦楽団があり、毎年1回ファミリーコンサートを開催しています。開催時期は大体 5 月です。コンサートのチケットは、開催日より約 1ヶ月前から無料配布していますので、ご興味のある方は来年 4 月になったら https://msjso.org を覗いてみてください。そして、今年のコンサートでは、WDK チームのさなえすさんが合唱団の一員として華麗なるデビューを果たしました!!もちろん当日コンサート会場に駆けつけましたが、合唱団は舞台の一番後ろに立たされ、しかも皆さん同じ服装をしていたので、残念ながら、どれがさなえすさんなのかは全く分かりませんでした。でも、きっと得意の語学力を存分に生かし、「乾杯の歌」、「第九」などをイタリア語、ドイツ語で歌っていたに違いないと思います。

次に、先週ある加工機のメーカ様のところにお邪魔しました。そこで、高精度・高剛性の頂点に立つ巨大な加工機を見せてもらい、普段携わっているコンピュータソフトウェアと全く別の世界に存在する「ものづくり」の美しさに魅了されました。特に印象深いのは、加工済みサンプルの中にある一枚の鏡でした。その鏡は、なんと、このメーカ様の加工機の精密な動きにより、金属の塊から削りだしたものです。鏡であるゆえに、少しでもゆがみなどがあればすぐに「ばれて」しまいますが、その鏡は完璧なものでした。その鏡の前で感嘆しつつ、Windowsがこんなところでも役に立っていることをとても誇りに思いました。

さて、本題のファイル I/O の API に入ります。今日お話したいテーマは2つあり、メモリマップドファイル非同期処理です。

--------------------------------
メモリマップドファイル
--------------------------------
ファイルデータをリードする場合、以下の2つの方法があります。

1. ReadFile方式
CreateFile -> ReadFile/ReadFileEx -> CloseHandle

2. メモリマップドファイル方式
A. CreateFile -> CreateFileMapping -> MapViewOfFile/MapViewOfFileEx を使って、ディスク上にあるファイルを仮想メモリにマッピングする
B. MapViewOfFile/MapViewOfFileEx から返されるアドレスに対して、普通のメモリアクセスを行う
C. リードが終わったら、UnmapViewOfFile -> CloseHandle(マッピングハンドルのクローズ) -> CloseHandle (ファイルハンドルのクローズ) を行う

アプリケーションレベルではこの2つの方法のどれでも、ファイルデータのリードが簡単にできて、違いは呼出し手順だけですが、ファイルシステムフィルタドライバにとっては、この2つの方法は天と地の違いがあります。

ReadFile 方式の場合、アプリケーションがReadFileを呼び出す時点で、フィルタドライバにFastI/O か、IRP_MJ_READ のキャッシュI/O リクエストがディスパッチされますが、メモリマップドファイル方式の場合、この2種類のキャッシュI/Oリクエストのどれもがフィルタドライバにディスパッチされません。なぜなら、アプリケーションがファイルデータのマッピングされているメモリに直接アクセスしているだけです。

幸いなことに、ファイルシステムフィルタドライバにとって、この2つの方式に共通するところがあります。どの方式においても、データを得るのにディスク上にあるファイルからデータを読み込む必要があるので、このディスクからファイルを読み込むリクエストは、共通項になります。このようなリクエストは、ページングI/Oとも呼ばれています。

この2つのファイルアクセスの方式が一因となり、ファイルシステムフィルタドライバではキャッシュI/Oリクエストではなく、ページングI/O(とノンキャッシュI/O)をフィルタリングするのは一般的なつくりとなっています。

メモリマップドファイル方式をあまり馴染んでいない方もいると思いますが、Windowsのメモ帳(Notepad.exe)がこの方式を使用してファイルのリード・ライトを行っています。「自作のフィルタドライバで、普通のエディタで問題なく動きますが、メモ帳だとうまく行かない」と言う類の問題に遭遇したら、フィルタドライバのどこかがメモリマップドファイル方式にうまく対応できていない可能性が高いと思っていただければと思います。

メモリマップドファイルのユーザモードAPIについて、以下のMSDNドキュメントをご参照ください。
https://msdn.microsoft.com/en-us/library/aa366556.aspx

-----------------
非同期処理
-----------------
ここで言う非同期処理は、アプリケーションが I/O リクエストを発行し、その I/O リクエストの完了を待たずに他の処理を行うことを指します。

非同期処理について、すぐにぴんと来ない方がいるかもしれませんが、分かりやすい例で言いますと、宅配ピザのお店に電話でピザを注文し、その後、ピザが届くまでの約 30 分間、玄関の外でじっと待つのではなく、Xbox でゲームをやったり、Windows 7 の RC 版をインストールしたり、Bing.com で検索したりして、待機時間を有効に使うのは、一種の非同期処理と言えます。

ピザの注文の例と同じ理由ですが、アプリケーションレベルで非同期的 API をうまく使うことによって、アプリケーションの操作性とパフォーマンスを向上させることが可能です。例えば、三つの異なる物理ディスク上にあるファイルに対して、非同期的 ReadFile を 3 回呼び出し、WaitForMultipleObjects 関数で 3つの I/O の結果を待つほうが、ReadFile を呼び出したつど結果待ちを行うことよりは、パフォーマンスが遥かにいいはずです。

この非同期処理の仕組みの根幹にあるのは、カーネルモードドライバの STATUS_PENDING に関わる処理です。ドライバで時間のかかる IPR を処理する際に、ディスパッチルーチンでその IRP をキューイングして、STATUS_PENDING を返すようにしますと、制御が I/O 発行元のアプリケーションに戻り、非同期処理が可能になります。

しかし、もしそのドライバの上にフィルタドライバがあり、そのフィルタドライバではこの非同期処理のルールを無視して、STATUS_PENDING のステータスに対してIRP が完了されるまで待つように実装されていますと、アプリケーションのつくりがどんなに優れても、非同期処理ができなくなってしまいます。

あるハードウェアのデバイスドライバでこの問題が起きても影響範囲がそんなに広くないかもしれませんが、ファイルシステムフィルタドライバでこのような間違った実装が行われますと、ただ1つのフィルタドライバによってシステム全体のパフォーマンスが大きく低下する事態となります。

また、IRP の種類にも依存しますが、パフォーマンスの低下だけではなく、システムがハングアップしてしまうことも考えられます。例えば、 IRP_MJ_DIRECTORY_CONTROL / IRP_MN_NOTIFY_CHANGE_DIRECTORY の IRP に対して、ファイルシステムがその IRP をキューイングしてから、 STATUS_PENDINGを返します。ファイルシステムの上位にあるフィルタドライバが、もしこのSTATUS_PENDINGに対して、完了待ちを行いますと、OSのシャットダウンまでこのスレッドがフィルタドライバの中でブロックされてしまう可能性があります。理由は、IRP_MN_NOTIFY_CHANGE_DIRECTORY の IRP は、ターゲットとなるディレクトリに変化が起きないと、完了されないからです。

STATUS_PENDINGに対して完了待ちを行うのは、それなりの理由があると思います。例えば、ドライバの完了ルーチンが DISPATCH_LEVEL で呼ばれる可能性があるので、呼び出してはいけない関数が存在します。だから、ディスパッチルーチンで完了待ちを行えば、PASSIVE_LEVEL になりますので、どんな関数を呼んでも問題ない、という考え方です。しかし、フィルタドライバの処理が簡単になるからと言って、I/O の非同期処理の通り道を塞ぐのが、良いデザインとは言えません。ファイルシステムフィルタドライバは、他のドライバ以上に、システム全体のパフォーマンスに影響を与えますので、デザインを慎重に検討していただきたいと思います。

ここまで書いて、よく考えてみたら、結局言いたかったのは、いつ来るかは分からないピザを足が棒になるまで外でじっと待つことのないように、ファイルシステムフィルタドライバが STATUS_PENDING のステータスをそのまま返すべきことですね。

それでは、皆さん、楽しくファイルシステムフィルタドライバを作りましょう!

フニクリ・フニクラ♪