#navi_header|技術| "WindowsOS内部のアーキテクチャのすべて"の第4章の要約、読書メモとなります。 本記事では以降、"WindowsOS内部のアーキテクチャのすべて" を "Windows OS Internal Architecture" として "WOIA" と略します。 #amazon||> ||< #more|| #outline|| ---- * (4章) Intel 80286 (16bit時代, 1982 - ) と Windows 3.0/3.1時代 1982年、アドレスバスを24bitに増やし、プロテクトモードを搭載した 80286が発表される。 プロテクトモードやセグメントディスクリプタによるアドレス指定、CPUの動作レベル(「特権」「リング0」~「リング3」)についても80286が発祥となっており、まずはIntel 80286の機能について簡単に紹介する。 続いてWindows3.xを取りあげ、80286上でどのようにプロテクトモードを活用したかをまとめる。 ** Intel 80286 80286のスペック: | アドレスバス | 24bit | | 汎用レジスタ | 16bit | | 外部データバス | 16bit | | 内部データバス | 16bit | bit幅に注目する限り、アドレスバスが24bitに増えた以外は8086と変わらないように見える。 しかし80286は以下の2大機能が登場した事で、続く80386の基礎を作った重要なCPUである。 : プロテクトモード : #block||> 24bit(16MB)のアドレス幅全てを使うにはプロテクトモードを使う必要がある。プロテクトモードでは、後述のセグメントディスクリプタを使ったアドレス指定方式により、bit幅の組み合わせ上は(=論理上は)1GBまでのアドレスを16MBの物理メモリに割り当てることが可能になっている。 プロテクトモードでのメモリや割り込みベクタ管理に必要な"ディスクリプタテーブル"(Descriptor Table)が登場したのも80286からである。 さらに、プロテクトモードで扱うデータの随所で「Privilege Level」を導入することでCPUの動作レベルを4段階に分け、アクセス出来るメモリや実行可能な命令を動作レベル毎に制限することが可能になった。これこそが「プロテクト」と呼ばれる所以である。 なお、80286ではリアルモードからプロテクトモードへ移行することは出来たがその逆、プロテクトモードからリアルモードへ戻ることは出来なかった(リセットする必要があった)。 ||< : マルチタスク機能 : "Task State Segment"(TSS)と"Task Register"(TR)により、複数のタスクを切り替えて実行出来るようになった。タスク切り替えのトリガーとしてはソフトウェア割り込みやJMP/CALL/IRETなど。またこれにより、タスク毎のメモリ空間を持てるようになった。 以下、Web上で入手出来た80286(M80C286)のデータシートに沿いつつ、汎用レジスタの構成や上記新機能の概要と関連する命令セットなどを簡単に紹介していく。 *** 内部レジスタ構成 基本的に8086/8088のレジスタ構成と同じである。全て16bitで構成されている。 プロテクトモードやタスク切り替え機能の追加に伴い、"MSW"(Machine Status Word)レジスタが追加されている。 #pre||> +---------+ +---------+ | AH | AL |=AX : ACCUMULATOR | S P | STACK POINTER +---------+ +---------+ | BH | BL |=BX : BASE | B P | BASE POINTER +---------+ +---------+ | CH | CL |=CX : COUNT | S I | SOURCE INDEX +---------+ +---------+ | DH | DL |=DX : DATA | D I | DESTINATION INDEX +---------+ +---------+ +---------+ +---------+ | C S | CODE SEGMENT | I P | INSTRUCTION POINTER +---------+ +---------+ | D S | DATA SEGMENT | FLAG | STATUS FLAGS +---------+ +---------+ | S S | STACK SEGMENT | MSW | MACHINE STATUS WORD +---------+ +---------+ | E S | EXTRA SEGMENT +---------+ ||< MSWレジスタは次のような構成になっている。 #pre||> 15 3 2 1 0 +----+- -+----+----+----+----+ MSW: | | ... | TS | EM | MP | PE | +----+- -+----+----+----+----+ TS : Task Switch EM : Processor Extnsion Emulator MP : Monito Processor Extension PE : Protection Enable ||< タスク切り替えではTSビットを使い、プロテクトモードへ切り替えるにはPEビットを1にする。初期状態ではPEビットは0、つまりリアルモードで動作するようになっている。 MSWレジスタを読み書きするために、以下の命令が追加されている。 LMSW : Load Machine Status Word from register/memory SMSW : Store Machine Status Word その他、プロテクトモードで使われる「ディスクリプタテーブル」のアドレスを格納する為の内部レジスタが複数追加されているが、これらは専用の命令を使って操作するようになっている。 *** リアルモードでのアドレス指定とメモリレイアウト 初期状態、つまりリアルモードでのアドレス指定は8086/8088と同じである。 16bitのセグメントレジスタ(CS, DS, SS, ES)を4bit左シフトしてセグメントアドレスとし、オフセットアドレスと組み合わせる。 CS, SSレジスタは特定のポインタレジスタと組み合わせる。 CS : Code Segment → IPと組み合わせる。 SS : Stack Segment → SP, BPと組み合わせる。BPからの相対アドレスを指定する時に使われる。 DS, ESレジスタについてはメモリ上のデータを指定する時に汎用的に使われる。 このように大きく「実行コード」「データ」「スタック」の3種類にメモリ領域を分割するCPUの機能は、プログラムの構成にも影響を及ぼしている。特にOSが実行可能ファイルをロードしてメモリ上に展開する際に、実行可能ファイルのフォーマットおよびその解析・展開処理でかならずこの3種類のメモリ領域が関わってくる。 リアルモードでのアドレス指定では、8086/8088と同様にアドレスバスを20bitまでしか使えない。つまり指定可能な物理メモリの上限は1MBのままである。 CPUが関わるメモリレイアウトについては、80286起動時点ではリアルモードであり、8086/8088のメモリレイアウトと同一である。 0 - 3FFhまでが割り込みベクタとしてCPU予約領域となり、FFFF0hがCPUリセット後に最初にフェッチされる命令のアドレスとなる。 プロテクトモードを利用することで、割り込みベクタ領域を任意の領域に指定することが可能となった。(もちろん、プロテクトモードでも従来通り0 - 3FFhを割り込みベクタ領域として指定出来る) *** "特権レベル"(Privilege Level)について 80286ではアクセスコントロールを強化する為に"特権レベル"(Privilege Level)という仕組みが導入されている。 メモリアクセスや実行アドレスの切り替え(JMP, CALL, 割り込み)において、CPUの現在の特権レベルと、アクセスするメモリやジャンプ先のアドレスの特権レベルを調べ、適切なアクセスコントロールを行う。また実行出来る命令の種類についても、現在の特権レベルで実行可能な命令かチェックされるようになる。 80286以降では次の4つのレベルが存在する。 レベル0 : "KERNEL"レベル レベル1 : "SYSTEM SERVICE"レベル レベル2 : "OS EXTENSIONS"レベル レベル3 : "APPLICATIONS"レベル "レベル"は"リング"と呼ばれる時もあり、「リング0」~「リング3」と呼ばれる場合もある。 レベルの数が小さいほど使える機能が多く、アクセス権限も強くなる。レベル0ではCPUの全機能を利用出来る。ハードウェアやCPUの機能の全てにアクセスするOSのカーネル部分は、一般的にレベル0で動作する。ユーザープログラムは一般的にレベル3で動作する。 レベルの数が大きい特権レベルは、それより数の小さい特権レベルの機能・メモリにはアクセス出来ない。レベル3の状態では、レベル0のメモリにはアクセス出来ない。その逆は可能である。 80286以降のプロテクトモードでは、メモリアドレスが出てくるところでは必ず、後述の「セグメントディスクリプタ」が使われる。セグメントディスクリプタの中にはそのメモリセグメントの特権レベルを表す2bitのフィールドが存在する。メモリアクセスやJMP/CALL/割り込みによりジャンプする場合には、CPUによりセグメントディスクリプタ中の特権レベルが自動的にチェックされることになる。 *** プロテクトモードのアドレス指定(セグメントディスクリプタを使ったセグメント方式) プロテクトモードにおいても、セグメントレジスタでセグメントを指定し、オフセットを加える仕組みはリアルモードと同じである。しかしセグメントレジスタによるセグメントの指定方式が大きく異なる。 : Real Mode : セグメントレジスタの値により、セグメントアドレスを直接指定していた。 : Protected Mode : セグメントレジスタは「セグメントディスクリプタ(Segment Descriptor)」の番号を指定するようになる。実際の物理アドレスは、指定された「セグメントディスクリプタ」のbitフィールドからCPUが自動的に読み取る。 セグメントディスクリプタは64bit=8バイトで構成され、以下のようなbitフィールドになっている。 #pre||> +--------------+--------------+ 7 | (RESERVED) | 6 +--------------+--------------+ 5 |P|DPL|S|TYPE|A|BASE (23 - 16)| 4 +--------------+--------------+ 3 | BASE (15 - 0) | 2 +--------------+--------------+ 1 | LIMIT (15 - 0) | 0 +--------------+--------------+ 15 7 0 ||< : BASE (0 - 23) : 24bit, セグメントの先頭アドレスを物理メモリアドレスで指定する。 : LIMIT (0 - 15) : 16bit, セグメントのサイズを指定する。 : P(Present) : 1bit, 物理メモリマッピング済フラグ : DPL(Descriptor Privilege Level) : 2bit, セグメントの特権レベル : S(Segment Descriptor) : 1bit, コードorデータセグメントの場合は1, 割り込みハンドラやTSSなどのシステムセグメントの場合は0。 : TYPE : 3bit, コード or データセグメントの場合に、セグメントの属性(実行可能やRead/Write)を指定する。 : A(Accessed) : 直近アクセスフラグ これを必要とするセグメントの分だけ用意した、セグメントディスクリプタの一覧が「 ''セグメントディスクリプタテーブル(Segment Descriptor Table)'' 」である。セグメントディスクリプタテーブルは、リアルモードの段階で用意しておき、その先頭アドレスをLGDT(Load Global Descriptor Table)/LLDT(Load Local Descriptor Table)によりCPUに登録しておく。 LGDT/LLDT命令では、セグメントディスクリプタの領域をBASE+LIMIT形式で指定したメモリ領域を指示する。 #pre||> LGDT命令の例(LLDTの場合も指定するメモリ領域の構造は同じ) +--------------+--------------+ 5 | (RESERVED) | BASE (23-16) | 4 +--------------+--------------+ 3 | BASE (15 - 0) | 2 +--------------+--------------+ 1 | LIMIT (15 - 0) | 0 ------> LGDT +--------------+--------------+ | 15 7 0 | | メモリ: | | (...) | | +---+------+<------+ | | | DT-n | | | | G +------+ | +---[ CPU ]-----------|---------+ | | ... | | | | | | D +------+ | | V | | | DT-2 | | | +--------------------------+ | | T +------+ +------| GDT LIMIT (16bit = 64KB)| | | | DT-1 | | +--------------------------+ | +---+------+<-------------| GDT BASE 24bit物理メモリ | | | (...) | | +--------------------------+ | +-------------------------------+ ||< CS, DSなどのセグメントレジスタでは、プロテクトモードにおいてはセグメントディスクリプタテーブル上のインデックス番号を使ってセグメントディスクリプタを指定する。セグメントディスクリプタを選択することから、プロテクトモードにおけるCS/SSなどのセグメントレジスタのことを「セレクタ(Selector)」呼ぶ場合もある。 セレクタの16bitフィールドは、次のような構造になっている。 +-----------+----+-----+ | INDEX | TI | RPL | +-----------+----+-----+ 15 3 2 0 : INDEX : 13bitで8192個のセグメントディスクリプタを指定可能。 : TI(Table Indicator) : グローバルなディスクリプタテーブル(Global Descriptor Table)を使う時は"1", ローカルなディスクリプタテーブル(Local Descriptor Table)を使う時は"0" : RPL(Requested Privilege Level) : 特権レベル 以下に、CS(Code Segment)でINDEXが"2"を示した時の、セグメントセレクタとGDTの関係を示す。 #pre||> | (...) | +---+------+ | | DT-n | | G +------+ | | ... | Segment Selector (ex: CS, Code Segment) | D +------+ +-----------+----+-----+ | | DT-2 |<-------| |1|0| TI | RPL | | T +------+ +-----------+----+-----+ | | DT-1 | 15 3 2 0 +---+------+ | (...) | ||< Segment SelecotrのINDEXサイズは13bit、つまり8192個のセグメントディスクリプタを指定出来るのは前述の通りである。 従って、一つのディスクリプタテーブルには8192個のセグメントディスクリプタが格納出来る。ディスクリプタは一つにつき8バイトなので、一つのディスクリプタテーブルの最大サイズは 8192個 x 8バイト = 65536 = 64KB となり、丁度LGDT/LLDTで指定可能なディスクリプタテーブルの最大LIMIT値(16bit)と同じになる。 「1GBを16MBの物理メモリにマッピングできる」という表現は、単純に セグメントセレクタの INDEX フィールド → 13bit 同 TIフィールド → 1bit オフセット → 16bit の計30bit幅だけ「セグメントセレクタ:オフセット」のbit幅があるので、"論理上は" 1GBを表現出来る、という意味である。 *** プロテクトモードとディスクリプタテーブルのその他の話題について これまで紹介したのは、主にCS/SS/DS/ESで使われるセグメントを指定する場合のディスクリプタテーブルの構造である。 80286ではこの他に、割り込みハンドラやタスク切り替えで使われる"System Segment Descriptor", "Gate Descriptor"というディスクリプタが存在する。割り込みベクタ用に"Interrupt Descriptor Table"を登録することも出来る。これらのディスクリプタテーブルの操作用に"LIDT(Load Interrupt Descriptor Table)"命令などが追加されている。 これらについては、"WOIA"の要約である本記事では取り扱わない。これら割り込みやタスク切り替えに使われる特殊なディスクリプタテーブルについては、OS開発やアセンブラプログラミングの話題に属するからである。 一点だけ注意しておくとしたら、 ''プロテクトモードでのソフトウェア割り込み(Gate Descriptor)を使うと特権レベルを変更出来る'' 事だろう。リング3で動作していたプログラムが、ソフトウェア割り込み(INT命令)をかけることでリング0に切り替わり、OSカーネルの機能を実行することができる。80286以降のプロテクトモードに対応したOSでは、基本的にこの仕組みで「システムコール」を実現している。 *** 80286のプロテクトモードでも存在した64KBの壁 アドレスバスが24bitになり、プロテクトモードが導入され、セグメント自体は1MBを超えて16MBまでに配置することが出来るようになった。 しかし ''オフセットを指定する汎用レジスタ群が16bitのままだった'' ため、80286の時点でも「一セグメントあたり64KBの壁」は存在している。 これは80286対応のWindows 3.xのメモリ管理方式に影響を与えている。 以上で80286プロテクトモードに関する必要な予備知識はまとめ終えた。いよいよ、1980年代後半~1990年代前半の流れとからめてWindows 3.xの内部構造を俯瞰していく。 ** 1980年代後半~1990年代前半の Windows2.x/Windows3.x の流れ 1980年代後半~1990年代前半は、8086/8088/80286/80386(1988年発売)搭載マシンが同時に存在した時代であり、OSもDOS/Windows2.x/Windows3.xが矢継ぎ早に発表されると同時に次々と新バージョンが投入、なおかつ旧バージョンもまだまだ現役というめまぐるしい時代だった。1987年には386DXを搭載した IBM Personal System/2 Model 80 (8580-xx) が発売されている。 なお、Windows3.x以前まではMS-DOSの拡張製品として販売されていた為、MS-DOSとWindowsをそれぞれ購入する必要があった。 : Windows 2.10 : 1988年発表。当初は80286対応だったが、後に80386にも対応する。80286に対応した版が"Windows/286", 80386に対応した版が"Windows/386"という名前に変更される。HIMEM.SYS登場により、HMAおよびXMSが公式に対応される。 : Windows 2.11 : #block||> 1989年発表。Windos 2.10のマイナーアップデート版。 ここでWindows 2.x系列について少しだけ整理しておく。 + Windows 2.0は元々8086/8088に対応しており、ハードウェアタイプのEMSを利用出来る、リアルモードのプログラムだった。 + Windows 2.10以降は Windows/286, Windows/386と分岐した。 + Windows/286については Windows 2.0 と同様リアルモードのプログラムだったようだ。しかしHIMEM.SYSの導入でHMA/XMSが使えるようになるなど、一部80286のプロテクトモードの機能の活用が始まっている。 + Windows/386は、MS-DOSアプリを仮想86機能でマルチタスクで動作出来るようになってたらしい。Windows用のアプリケーションについては、アプリ自身が制御をOSに戻す協調型マルチタスク(ノンプリエンプティブ)になっていた。この棲み分けはWindows3.xに引き継がれている。また、ソフトウェアエミュレーションタイプのEMSが利用出来るようになっている。 ||< : Windows 3.0 : #block||> 1990年発表。CPUに応じた三つのモードで動作するようになっていた。 + 8086/8088用 "Real Mode" : リアルモードで動作する。 + 80286用 "Standard Mode" : 80286のプロテクトモードを利用する。 + 80386用 "386 Enhanced Mode" : 80386のプロテクトモードおよび仮想86機能を利用する。 HIMEM.SYSがXMS仕様2.0に対応。EMM386.SYS導入。 ||< : Windows 3.1 : 1992年発表。この時点で80286以上、80386が推奨になり、 ''8086/8088用のリアルモードが無くなった。'' 日本語版Windows 3.1では80286対応も外され、Standard Modeでも80386以降が必須となった。HIMEM.SYSがXMS仕様3.0に対応。EMM386.EXE導入。 Windows 2.x/3.x/95に至まで、基本的にはまずDOSが立ち上がり、その後Windowsが起動する仕組みになっている。この場合、HIMEM.SYSはDOSカーネルをExtended Memory(HMA含む)以降にロードするだけの役割である。しかしEMM386についてはプロテクトモードのメモリを操作する為、Windows3.0のStandard Modeと競合してしまう。このためEMM386.SYS/EMM386.EXEはWindowsを使う時は無効化する必要があった。 参考: - Cannot Use EMM386.SYS/EMM386.EXE in Standard Mode Windows 3.0 -- http://support.microsoft.com/kb/82081/EN-US/ 以降はWOIAに沿ってWindows3.xの構造を追っていくが、こうしてWindows2.xまで俯瞰してみると、Windows/386をベースにWindows3.xが出来たようにも見えてくる。協調型マルチタスクや、仮想86機能によるDOSアプリのマルチタスク機能についてはWindows/386がベースとなっているようだ。 ** Windows 3.xのブートシーケンス(WIN.COM + DOSX.EXE/WIN386.EXE) Windows 3.xまではMS-DOSと別で販売されていた。このため、パソコンを起動して最初に立ち上がるのはあくまでもリアルモードのMS-DOSであった。 Windowsを立ち上げるには、MS-DOSのコマンドプロンプトから ''"WIN.COM"'' を実行する。 WIN.COMが実行されると、CPUが80286の場合は ''DOSX.EXE'' というプログラムを実行し、CPUが80386以上の場合は ''WIN386.EXE'' が実行される。以降は80286用である"DOSX.EXE"について話を進める。 MS-DOS -> "WIN.COM" --+-- 80286 --> "DOSX.EXE" ---> 286版のWindows 3.xが起動 | +-- 80386 --> "WIN386.EXE" -> 386版のWindows 3.xが起動 WIN.COMまではCPUはMS-DOS、つまりリアルモードで動作している。DOSX.EXEが実行されると、その内部でPEビットをONにし、プロテクトモードに移行する。同時に各種セグメントディスクリプタテーブルを作成・登録し、プロテクトモードでのメモリ管理の準備を整える。 なお、Windows全般(Win3.x/9x/NT/2000以降含む)でシステム起動時にセグメントディスクリプタテーブルを作成し、登録している。 DOSX.EXEは引き続きメモリ上に常駐するが、その特権レベルは0、つまりリング0で動作することになる。 *** Windows 3.x起動完了後のメモリレイアウト Windows 3.x起動完了後のメモリレイアウトは次のようになっている。 #pre||> +---------------------------+ 16MB | Win16アプリケーション領域 | +---------------------------+ | Windows 3.x システム | | (DOSX.EXE, 他) | +---------------------------+ 1MB | VRAM/ROM/BIOS領域 | +---------------------------+ 640KB | (ユーザ領域) | | Win16API | | (KRNL286,USER,GDI) | +---------------------------+ | MS-DOSシステム領域, | | 割り込みベクタ | +---------------------------+ 0KB ||< *** 参考資料 - WIN.COM -- http://en.wikipedia.org/wiki/WIN.COM - Windows 95/98 Win.com Command-Line Switches -- http://support.microsoft.com/kb/142544/EN-US/ ** Win16 API Windows 3.xになり、これまでのソフトウェア割り込みによるシステムコールに代わり、"Win16 API"という仕組みが広く使われるようになった。以降、WindowsアプリケーションはOSの機能を使うにはこの "Win16 API" (プログラミングレベルでは、Microsoftが提供するライブラリ関数として呼び出す)を使うことになる。 *** DPMI(DOS Protected Mode Interface)について Win16APIに話を進める前に、DPMIについて簡単に触れておく。 1984年頃からメモリ不足を解消する為に様々な技術仕様とプログラムが開発された。当初はバンク切り替え方式の専用メモリカードを使ったExpanded Memoryとそのデバイスドライバが使われていたが、1984年の時点で既に80286を搭載したIBM PC ATが発売されており、翌1985年には80386が発表されている。つまりプロテクトモードによる1MBを超えるメモリアクセスが技術的には可能となっていた。1987年、80286以降のプロテクトモードでアクセス出来るメモリを使う為の技術仕様、EMS 4.0 (Lotus, Intel, Microsoft, AST合同)が策定される。このころからプロテクトモードの機能を使ってDOSを拡張する、"DOS extender"と呼ばれるタイプのソフトウェアが広まっていく。 しかし80386以降の仮想86機能を使ったEMSでは、既存の"DOS extender"と共存させる上で幾つかの問題が発生した。 Wikipediaより抜粋: - MS-DOSが仮想86モードで動作しているために、リアルモードからプロテクトモードへの切替えを想定されて開発されたDOSエクステンダは、特権命令を使用できないためプロテクトモードへ切替える方法が無かった。 - ソフトウェアEMSが全てのプロテクトメモリを獲得してしまうためにDOSエクステンダが利用可能なプロテクトメモリが存在しなかった。 - プロテクトモード環境下では、割り込みコントローラーはリアルモードと異なる設定をしなくてはならないが、標準的な管理方法が無かった。 これらの問題を解決して、仮想86機能によるEMSとDOS extenderを共存させる為の技術仕様が、 Quarterdeck Office Systems と Phar Lap Software, Inc. の間で策定された。 これが1989年発表の "Virtual Control Program Interface", VCPI, Version 1.0 である。EMS 4.0 規格の int 67h ファンクションコールを拡張する形でEMSマネージャーにVCPI サーバが実装された。 しかしVCPIを使ったプログラムではプロテクトモードでもリング0で動いてしまうという問題があったり、翌1990年に発表されたWindows 3.0の386 enhancedモードではVCPIがサポートされなかった事情もあり、Windows 3.0で実装されたDPMIに押される形でVCPIは廃れていってしまう。 "DOS Protected Mode Interface"(DPMI)とは、DOSプログラムがプロテクトモードで動作し、プロテクトモードで提供される機能を利用する為の技術仕様である。 1989年にドラフト(草案)が発表され、1990年にVersion 0.9が発表される。同年、DPMI 0.9を実装したWindows 3.0が発表される。 1991年に0.9で不足していた部分を解消したVersion 1.0が発表されたが、Microsoftは対応していない。そのため、多くのDPMIベースのDOS extenderもVersion 0.9に準拠した。 VCPIはINT 61hをベースに拡張しているが、DPMIはINT 31hを中心として拡張しており、仕様的には別系統に属する。 DPMIは基本的に仮想86機能を使ってDOSアプリケーションをプロテクトモードで動作させるが、部分的にプロテクトモードで動作させることも仕様としては可能だった。そのため「DPMI = 仮想86機能」というのは若干不正確に思われる。 またDPMIでは「クライアント・サーバー」という図式がより明確化された。例えば、DPMI 0.9の仕様書には次のような図が掲載されており、Client/Serverという関係を強く印象づけている。 #pre||> Figure 1. Application/Extender/Client/Host/OS structure +----------------------------------------------------------+ | | | +----------------------------------------------------+ | | | | | | | Application Code | | | | | | | +----------------------------------------------------+ | | | | +----------------------------------------------------+ | | | Extender Base (including APIs) | | | | -------------------------------------------------- | | | | DPMI | | | | client | | | +------------+ | | | | VCPI | | | | client | | | +------------+ | | | | XMS | | | | client | | | +------------+ | | | | Top-down | | | | client | | | +-------------+ | | | +----------------------------------------------------------+ +------------+ | | | | | |------------+ | | | | DPMI | | | host | VCPI |------------+ | | | | | | | | | |------------| XMS |-------------+ | | EMS | | Top-down | | | | | (Int 15h) | +----------------------------------------------------+ +----------------------------------------------------+ | | | Operating System (e.g. DOS) | | | +----------------------------------------------------+ ||< 上図で"OS"部分が"e.g. DOS"とあるとおり、OSとしてDOSだけを想定していたわけではない(事実、最初の実装はWindows 3.0)。 DPMIをコールするアプリケーション(DPMI Client)側からは特にOSを意識することはなく、DPMIを実装したDOS extenderあるいはOS(Windows)がDPMI Server(host)として実際の処理を行う。そう言う意味では、DPMIはプロテクトモードのメモリ管理や機能に特化した仮想環境と呼ぶことも可能である。 参考資料: - Using VCPI Programs with Windows -- http://support.microsoft.com/kb/81493/EN-US/ - INFO: The Windows Developer's Notes -- http://support.microsoft.com/kb/65260/EN-US/ - FILE: Windows Int 21h and NetBIOS Support for DPMI -- http://support.microsoft.com/kb/65128/EN-US/ Wikipediaショートカット: - VCPI: -- http://ja.wikipedia.org/wiki/VCPI -- http://en.wikipedia.org/wiki/Virtual_Control_Program_Interface - DPMI: -- http://ja.wikipedia.org/wiki/DPMI -- http://en.wikipedia.org/wiki/DOS_Protected_Mode_Interface *** Win16 APIの登場 "Win16 API"は16bitプログラム用の実装であり、"Win16"という呼称も"Win32"が登場してから付けられたようだ。 Windows 1.0の時代から存在していたらしく、Windows 1.0の時点で "GetMessage()", "GlobalAlloc()" 等のAPIが提供されていたようだ。 しかし、 ''Windows 2.0までは明らかにリアルモードのOSであり、'' プロテクトモードのメモリ管理や特権レベル機能を活用した構造にはなっていない。 Windows 3.xになって前述のDPMIに沿った形に整理された。 参考資料: - History of the Windows API -- http://web.archive.org/web/20020802081510/http://www.iseran.com/Win32/FAQ/history.html - Windows API - Wikipedia -- http://ja.wikipedia.org/wiki/Windows_API -- http://en.wikipedia.org/wiki/Winapi *** Windows 3.x における Win16APIの仕組み MS-DOSまではCPUのソフトウェア割り込みを使ってMS-DOS.SYSの提供するOSの機能を呼び出していた(「システムコール」or「ファンクションコール」)。 一方のWin16 API、特に80286以降に対応したWindows 3.xにおいては、次のような仕組みになっている。 + アプリケーションがWin16APIを呼び出すと、APIの種類応じてKRNL286.EXE/USER.EXE/GDI.EXEの機能が呼ばれる。ここまではCPUの特権レベルはリング3で、まだCPUのメモリ管理やハードウェアに関わる命令は使えない。 + KRNL286.EXEなどのWin16APIの実体プログラムは、DPMIクライアントとしてDPMIシステムコールを発行する。 + これにより特権レベル0, リング0で動作しているDOSX.EXEなどのカーネルプログラムに制御が移り、DPMI Server or Hostとしてセグメントの割り当てやハードウェア制御を行う。 #pre||> +--------------------------+ | Win16 API Application |<-- RING-3 +--------------------------+ | V +--------------------------+ | Win16 API (=DPMI Client) |<-- RING-3 | (KRNL286.EXE, USER.EXE, | | GDI.EXE, その他) | +--------------------------+ | [DPMI CALL] | V +--------------------------+ | Windows 3.x Kernel |<-- RING-0 | (=DPMI Server/Host) | | (DOSX.EXE, 他) | +--------------------------+ | V +--------------------------+ | CPU, Hardwares | +--------------------------+ ||< *** KRNL286.EXE/USER.EXE/GDI.EXE の役割分担 : KRNL286.EXE : メモリ管理・ファイル入出力などOSのコア部分 : USER.EXE : GUIコンポーネント管理など。グラフィック描画やファイル入出力に関わる部分はKRNL286.EXE/GDI.EXEに委譲する。 : GDI.EXE : グラフィックコントローラに対する文字・図形の描画命令を担う。 ファイルやネットワーク入出力(KRNL286.EXE), グラフィックコントローラへの描画命令(GDI.EXE)は、OSが直接ハードウェアとやり取りするわけではなく、デバイスドライバが間に入る格好になる。 *** Win16 API とマルチタスクの仕組み Windows 3.xまでは、アプリケーションがOSに制御を戻すことでマルチタスクを実現する「協調型マルチタスク」(ノンプリエンプティブ)になっていた。 アプリケーションA~Cが動いていた時、もしもアプリケーションBがハングアップしてしまいOSに制御を戻さなかった場合は、アプリケーションA、Cには永久に制御が渡らなくなってしまう。つまりどれか一つでもハングアップしてしまうと、他のアプリはもとよりOSにも制御が渡らなくなり、システム全体がハングアップしてしまう。 Windows9x以降はOS側が強制的に実行権を取得し、一つのアプリケーションがハングアップしても他のアプリやOSに制御が戻せるような、「プリエンプティブマルチタスク」になっている。 *** "Standard Mode" と "386 Enhanced Mode" Windows 3.xでは搭載するCPUによって3種類のモードが提供されていた。 : Real Mode : 8086/8088で動作する。Windows 3.1で廃止された。 : Protected Mode (スタンダードモード) : #block||> 80286で動作する。 - "WIN.COM" から "DOSX.EXE" が実行される。 - "KRNL286.EXE"が使われる。 - 仮想86機能による安全なMS-DOS実行環境は提供されていない。 ||< : 386 Enhanced Mode (エンハンスモード) : #block||> 80386で動作する。 - "WIN.COM" から "WIN386.EXE" が実行される。 - "KRNL386.EXE"が使われる。 - 仮想86機能による安全なMS-DOS実行環境が使える。 ||< *** 64KBの壁によるリソース不足 80286においては、プロテクトモードによりセグメントを16MBまでの領域に配置することが出来た。しかしオフセットが16bitのままだったので、1セグメント64KBの壁は存在していた。 これにより、Win16アプリケーションにおいても64KBの壁が存在していた。 問題になったのはKRNL286.EXE/USER.EXE/GDI.EXEも64KBの壁に縛られていた点で、マルチタスクにより複数のWin16アプリケーションが動作する場合、その分だけのリソースがKRNL286.EXE/USER.EXE/GDI.EXEの各64KB内で埋まってしまう。 各64KBの中で複数アプリのリソースをやりくりしなければならなかったため、リソース不足が頻繁に発生した。 *** まとめ このように80286のプロテクトモードを利用した Windows 3.x (Standard Mode)はDPMIを活用したユーザー空間とOS空間の分離モデルを定着させた。しかし64KBの壁により、リソース不足による不安定を招いた。 この後登場する80386により、64KBの壁が打破されると共に仮想86機能によるDOSプログラムの完全な分離が実現される。また、Windows9xにおいてはWindows NTから移植されたWin32APIが導入されると共に、プリエンプティブマルチタスクが実現される。これには80386で登場する仮想メモリ機能が活用されている。 * メモ:"A20ライン" 80286のメモリアドレスの指定では、アドレスバスの21bit目、A0から数えると "A20" ラインに注意する必要がある。A20ラインについてまとめる前に、8086/8088でのアドレス指定における"bug"について紹介する。 ** 8086/8088での FFFFFh 以降のアドレスについて 8086/8088のアドレスバスは20bit、つまり FFFFFh (1MB)がアドレスバスに出力可能な最大値である。 その指定方法は、セグメント16bit, オフセット16bit でセグメントを4bit左シフトして「加算」する方式だった。 しかし、加算した結果が FFFFFh を超える「セグメント:オフセット」の組み合わせが発生してしまった。 (A) 丁度 FFFFFh => FFFFh:000Fh → FFFF0h + Fh = FFFFFh (B) FFFFFhを"1"over => FFFFFh:0010h → FFFF0h + 10h = 100000h = FFFFFh + 1h 8086/8088では、繰り上がったbitを無視して20bit分だけを使う仕組みになっていた。上記 (B) のようなケースでは、繰り上がった21bit目を無視し、全体を"00000h"、つまり0番地アドレスとして物理メモリにアクセスしてしまう。これをbugと見なす資料もある。 この仕組みを利用して、一部のDOSプログラムには FFFFFh 以降のアドレス指定により、物理メモリ先頭の 0 - 64KB 空間を使うものが存在した。EMSの仕組みを使うよりも高速にKB単位のメモリ空間にアクセス出来た事が理由である。 ** 80286での FFFFFh 以降のアドレスについて 80286になりアドレスバスは24bitに拡張された。するとこれまで FFFFh:0010h で "00000h" と扱われてきたアドレスが、21bit目以降も認識されて本来の "100000h" として物理メモリにアクセス出来るようになった。 ところが、8086/8088の動作を期待していたDOSプログラムからすればまるで見当違いのアドレスにアクセスしてしまうことになり、80286で動作しなくなるアプリケーションが出てきてしまった。 80286は8086/8088と互換性を重視して開発されたが、21bit目以降の扱い("bug")までは互換性が維持されていなかった。 ** "IBM PC AT" から登場した "A20ゲート" 80286を採用した"IBM PC AT"でも、この非互換性が問題になった。最も単純な解決方法は21bit目以降を物理的に遮断することで、物理的に20bitに丸めてしまう方法だった。これにより8086/8088と同じ挙動になり、互換性は保たれる。 もう一つの解決方法としては、21bit目の有効・無効を何らかの仕組みで切り替えられるようにすることだった。この方法であれば、8086/8088互換モードと80286本来のアドレッシングモードを、排他的ではあるが両方使うことが出来る。 そこでIBMのエンジニアは、キーボードコントローラ8042の未使用pinを、ANDロジックで21bit目に接続した。 #pre||> [CPU] [RAM] A0 -------------------> A0 (...) A19 -------------------> A19 A20 ----+ | +-----+ +-->| | | AND |----> A20 +-->| | | +---- + | +---+---------+ | Keyboard | | Controller | | (8042) | +-------------+ A21 -------------------> A21 (...) ||< 初期状態では8042のANDロジックに繋がっているpinは0になっているため、8086/8088互換のアドレッシングモードになっている。プログラム側でI/Oポートを操作してpinを1にすると、80286本来のアドレッシングモードになる。 これが、"A20ゲート"と呼ばれる機構の始まりである。以降、メーカーによってはBIOSのinterruptや独自のI/Oポートで切り替えられるようにしたり様々に対応する。この結果、A20ゲートの切り替えは機種依存になってしまったが、特に統一されることなく現在に続いている。 ** A20ラインとEMS/XMSの関係 EMS4.0の段階では、EMSはA20ゲートを意識していない。もともと8086/8088時代のメモリ拡張仕様であるため、仕様としてはA20ラインは考慮されていない。一方のXMSでは、当初からHMAの利用を目的としていたこともあり、少なくともXMS2.0の段階でA20の切り替えが仕様に盛りこまれている。 80286以降のプロテクトモードのメモリ管理を活用するDOS extender、およびDPMIでもA20の切り替え機能が取り込まれている。OS開発という見地では、x86のプロテクトモードを使う為にはA20ゲートの有効化が必須となる。 ** 参考資料 - X86 Assembly/16 32 and 64 Bits (X86アセンブラ/16、32、64ビット) -- http://en.wikibooks.org/wiki/X86_Assembly/16_32_and_64_Bits -- http://ja.wikibooks.org/wiki/X86%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%A9/16%E3%80%8132%E3%80%8164%E3%83%93%E3%83%83%E3%83%88 - A20関連 -- http://en.wikipedia.org/wiki/A20_line -- http://en.wikipedia.org/wiki/A20_handler -- http://www.win.tue.nl/~aeb/linux/kbd/A20.html -- http://www.openwatcom.org/index.php/A20_Line - A20をONにするためのアセンブラコードSample -- http://www.visopsys.org/osdev/sources/enableA20.html - LOTUS(R)/INTEL(R)/MICROSOFT(R) EXPANDED MEMORY SPECIFICATION [1] Version 4.0 - eXtended Memory Specification (XMS), ver 2.0 - eXtended Memory Specification (XMS), ver 3.0 - DPMI Version 0.9 仕様書 #navi_footer|技術|