The Old New Thing: 実行ファイルのベースアドレスが0x00400000である理由
古参MS社員のRaymond Chenが、なぜWindowsのデフォルトのベースアドレスは0x00400000に配置されているのか。最初の4MBは何だという質問に答えている。
DLLのデフォルトのベースアドレスは0x10000000だが、なぜEXEのデフォルトのベースアドレスは0x00400000なのか。なぜこの値なのか。4メガバイトに何の意味があるのか。
これには、x86のディレクトリエントリひとつでマップできるアドレス空間であることと、1987年に決められた設計のためだ。
EXEのベースアドレスの制約としては、64KBの倍数であることのみだ。しかし、ベースアドレスを選ぶ理由には他にもある。
ベースアドレスを選ぶ目的は、モジュールが再配置される可能性を最小限に抑えることである。これはつまり、すでにそのアドレス空間にあるものと衝突させない(衝突にすると再配置しなければならない)ことと、後からそのアドレス空間に配置されるものと衝突しないということだ(後から来たものが再配置される)。実行可能ファイルにとって、後から配置されるものとの衝突を回避することとは、DLLによって占められる領域を回避することだ。OSはDLLを高アドレスに配置するし、OSのものではないDLLのデフォルトのベースアドレスは0x10000000なので、実行可能ファイルのベースアドレスは、0x10000000よりも低い場所であるべきで、低いほど、DLLと衝突するまでの領域が多くあることになる。しかし、どこまで低くできるのだろうか。
まず、すでにそこに配置されているものとの衝突を避けなけれあbならない。Windows NTは低アドレスにそれほどものを置かない。すでに配置してあるものは、nullポインターアクセスを補足するための、ゼロにマップしてあるPAGE_NOACCESSページだ。故に、Windows NTでは、実行可能ファイルのベースアドレスを0x00010000にすることができ、当時の多くのアプリケーションは実際にそうしていた。
しかし、Windows 95では、すでに多くのものが配置されていた。Windows 95のバーチャルマシンマネージャーは、CPUの不具合に対処するため、常に最初の64KBの物理メモリと最初の64KBのバーチャルメモリに配置されていた(Windows 95は、大量のCPUバグや、ファームウェアのバグに対処しなければならなかったのだ)。さらに、最初の1メガバイトのバーチャルアドレス空間は、バーチャルマシンの論理アドレス空間にまっぷされていた。(実際は、1メガバイトよりすこし多かった)。このマッピングは、x86プロセッサーのvirtual-8086モードに必要なものであった。
Windows 95は、その前身のWindows 3.1のように、Windowsを特別なバーチャルマシン上で動かしていて(System VMとして知られている)、互換性のために、あらゆるものを16bitコード上から通すようにしていた。偽物が正しく鳴くようにするためだ。そのため、CPUは(MS-DOSベースのアプリケーションではなく)Windowsアプリケーションを実行しているものの、バーチャルマシンのマッピングをいじしつづけているため、MS-DOS互換レイヤーのために毎回ページリマッピング(と、それに伴い高くつくTLBフラッシュ)を行う必要はなかった。
さて、最初の1メガバイトのアドレス空間はまな板の上から取り除かれているとして、他の3メガバイトはどうしたんだ。
さて、ここで記事の最初のヒントに戻る。
コンテキストスイッチを速くするため、Windows 3.1のバーチャルマシンマネージャーは、VMごとのコンテキストを4MBに切り上げていた。これにより、メモリーのコンテキストスイッチは、ページディレクトリのたったひとつの32bit値を書き変えるだけですんだ。(細かく言うと、インスタンスデータページもあるが、それは数十個程度のビットを反転させるだけだ)。この切り上げにより、3メガバイトのアドレス空間を失った。しかし、4ギガバイトものアドレス空間があるからには、十分の一パーセント以下の損失は、圧倒的なパフォーマンスの向上に比べれば、十分支払うべき価値のあるものだ(特に、この当時のアプリケーションで限界に挑むものなどなかった。当時のコンピューターは全体で2MBしかRAMがなかったのだから)
このメモリーマップは32-bit Windowsアプリケーションのための分離されたアドレス空間のための一部の変更がありながらも、Windows 95に引き継がれた。故に、実行可能ファイルがロードできる最も低いアドレスは4MBで、つまり0x00400000だ。
ギークトリビア:Win32アプリケーションがMS-DOS互換領域にアクセスするのを防ぐため、フラットデータセレクターは、実際には4MB境界で止まる下方に拡張するセレクターだ(同様に、16bit Windowsアプリケーションにおけるnullポインターは、nullセレクターが無効なため、アクセス違反を引き起こす。割り込みベクターテーブルにはアクセスしない)
リンカーは実行可能ファイルのデフォルトのベースアドレスとして0x0400000を選択するので、結果のバイナリはWindows NTとWindows 95の両方で再配置なしでロードできる。Windows 95対応はもはや誰も気にしないので、原理的には、リンカーの開発部署は、今ならば別のデフォルトベースアドレスを選べるはずである。しかし、グラフをちょっと綺麗にする以外の実質的な利点はない。それに、もし変えたとしたら、「なんである実行可能ファイルのベースアドレスには0x0400000のと、0x00010000のがあるんだ?」という質問が来るだけである。
またもや歴史の話。それにしても、今回のRaymond Chenの記事には、やたらとリンクがある。リンクされているページは、だいぶ昔のものが多いが、面白い。