MSDOSの時代には、ワイルドカード * と ? の展開は、アプリケーション側の仕事だった。command.com のコマンドラインで *.txt と入力されていれば、アプリケーション側にもそのままのかたちで渡されていた。Cでいったらargvに *.txt というのがそのまま入っていた。アプリケーションは自分でディレクトリのファイル一覧を取得して、パターンにマッチするものだけを取り出す必要があった。
UNIXではメタキャラクタの展開はシェルの仕事だ。シェルがexecvpなどを使ってアプリケーションを起動する前に、引数にメタキャラクタを含んだものがないかを探して、メタキャラクタが含まれていたらそれを展開してアプリケーションに渡す。そのため、展開した結果がでかかったりすると argument too long などと出て、シェルはアプリケーションを起動しなくなる。
Windowsになってワイルドカードを使える場面自体がほとんどなくなったが、いつのまにかWindowsでのワイルドカードの展開はファイルシステムの仕事になっていた。アプリケーションでもなくシェルでもなくてファイルシステムの仕事に。
コマンドラインで
dir e:\
としたとき、dirコマンドは e:\ がディレクトリかどうかを確認し、ディレクトリだとわかるとおそらく FindFirstFile を呼び出してファイルの一覧を取得する。第一引数に指定するファイル名にはA pointer to a null-terminated string that specifies a valid directory or path, and file name that can contain wildcard characters, for example, an asterisk (*) or a question mark (?).
IRP_MJ_DIRECTORY_CONTROL/IRP_MN_QUERY_DIRECTORY という、ディレクトリの一覧を返す処理をするリクエストになる。このときにもワイルドカードを含むファイル名が渡されてくる。カーネル内の FindFirstFile<=>IRP_MJ_DIRECTORY_CONTROL/IRP_MN_QUERY_DIRECTORY の変換を行う抽象化層でマッチングを行ってはくれないのだ。そのためファイルシステムドライバは自分でワイルドカードのマッチングを行って、一致するものだけを返す必要がある。さすがにファイルシステムごとにワイルドカードマッチングコードを書く必要はなく(カーネルモードで文字列処理をするコードを書くのはやや骨が折れる)、カーネルが FsRtlIsNameInExpression というAPIを提供していて、これを呼ぶことで与えた文字列が特定のワイルドカードを含むパターンにマッチするかを返してくれるようになっている。
Linuxの場合はVFSにreaddirというファンクションがあり、この関数を呼ぶと特定のノードのエントリすべてが返ってくるようになっている(とおもう)。おそらく、シェルはreaddirでディレクトリのすべてのエントリを取得し、ひとつひとつのエントリがパターンにマッチするかのテストを行って、マッチしたものをアプリケーションの引数として展開しているのだろう。
Windowsのディレクトリ内のファイル一覧取得に関する設計は、ファイルシステムドライバとカーネル間で転送されるデータ量を最小限にとどめ、パフォーマンスを改善することが可能だ。しかし、実際のところFindFirstFileがディレクトリ内のすべてのエントリを返す * 以外のパターンで呼ばれることはそう多くはないのではないだろうか。
結果としてUNIX系のOSよりもファイルシステムドライバに実装しなくてはいけないコードを増やしているように思える。
トラックバック元エントリにこのエントリへのリンクがない場合はトラックバックを受け付けません。
http://labs.gmo.jp/mt/mt-tb.cgi/98
comments