プロテクトモードとは、80286以降のx86アーキテクチャのCPUモードの一つ。正式名称は Protected Virtual Address Mode(保護仮想アドレスモード)である[1]。メモリやI/Oの保護を行うと共に、アドレス空間の拡張を行ったモードである。このモードでは仮想記憶、ページング、安全なマルチタスクといった機能をシステムソフトウェアが使えるようになり、アプリケーションソフトウェアへのオペレーティングシステム (OS) の制御能力が向上するよう設計されている[2][3]。
プロテクトモードとはIntel 80286以降のCPUの命令セットアーキテクチャの動作モードの一つであり、これらのCPUの本来の動作モードといえる物である。特徴づけるものは、その名前の通り、階層的な特権管理(リングプロテクション)や、タスク間のメモリ保護(プロテクト)を行う事が可能な事である。
プロテクトモードをサポートしているx86系プロセッサであっても、起動時のモードはリアルモードである[4]。つまり、「単に動作が高速であるというだけの、従来プロセッサ(すなわち、8086)」に見える。このことは、プロテクトモードに対応していない従来のOSをはじめとするシステムプログラムや、さらにはアプリケーションプログラムでも、そのまま新しいプロセッサを積んだマシンで使うことができる、という互換性を提供している[5]。プロテクトモードに移行するには、いくつかのディスクリプタテーブルを設定してから、コントロールレジスタ0 (CR0) のPE (Protection Enable) ビットをセットする[6]。
プロテクトモードは1982年、インテルの 80286 (286) プロセッサのリリースと共にx86アーキテクチャに追加され[7]、1985年の 80386 (386) で拡張された[8]。プロテクトモードの追加による機能向上によって広く採用され、同時にそれがその後のx86アーキテクチャの全ての強化の基礎となった[9]。
プロテクトモードにおいては、メモリ上のグローバルディスクリプタテーブル (GDT) およびローカルディスクリプタテーブル (LDT) という構造体の配列をそれぞれ最大8192個管理する。この構造体はそれぞれ、リニアアドレスのポインタおよび大きさおよび保護情報を持つセグメントディスクリプタや、タスクセグメントへのポインタとサイズ、保護情報を持つタスクディスクリプタ、ローカルディスクリプタテーブルへのポインタを保持するもの、そしてコールゲートと呼ばれる特権等を変更するための呼び先である、ゲートディスクリプタ等を持つ。セグメントレジスタはセレクタと呼ばれGDTまたはLDTのオフセットでセグメントディスクリプタを指すものとなる。無効なディスクリプタをセレクタにロードしたりすると例外を発生するようになった。タスクセグメントは、LDTRを含むレジスタ等の実行環境を保持する。
また、割り込み/例外ベクタも、最低位アドレスに固定されることなく、割り込みディスクリプタテーブル (IDT) にあるゲートディスクリプタの配列により設定されることになった。
80286以降で物理メモリの拡張も行われたが、100000h以降(1MB以降)のアドレスの物理メモリは、HMAを除けばプロテクトモードを使用しない限り、CPUからはアクセスできない領域であった。そのため、この実質的にプロテクトモード専用の1MB以降の物理メモリ領域は通称プロテクトメモリと呼ばれた(Extended Memory)。 プロテクトモードでは、コンベンショナルメモリ以外にプロテクトメモリも利用できるので、利用可能な物理メモリも増大した。この利用可能なメモリの増大もプロテクトモードを使用する利点の一つである。 プロテクトメモリを利用するためには、本来UNIXやOS/2等のマルチタスクOSが必要であるが、MS-DOS上でもEMS・XMSドライバ、DOSエクステンダ等を使用すれば利用可能であった。
286に先立つ Intel 8086 での本来の設計では、メモリアクセス用アドレスバスは20ビット幅だった[10]。これにより 220 バイト、すなわち1メガバイトのメモリにアクセス可能である[10]。当時は1メガバイトといえばかなり大容量のメモリという感覚だったので[11]、IBM PC の設計者らは先頭640キロバイトをアプリケーションとオペレーティングシステムで使用し、残る384キロバイトをBIOS (Basic Input/Output System) や周辺機器で使用する設計にした[12]。
元々、8086においては、セグメント方式の仮想記憶を意識したセグメントレジスタがあり、コード、データおよびスタックの量がそれぞれ64キロバイト以内であれば、ロードされた物理アドレスを意識すること無く、タスクの実行が可能であったが、論理アドレスから物理アドレスへの変換は単純にセグメントレジスタを4ビットシフトして足すと言うもので、タスク間の保護もなかった。
メモリ価格が低下し、メモリ使用量が増えてくると、1MBという制約が大きな問題となってきた。インテルはこの制限に対処するためもあって 286 をリリースした[12]。
最初に導入された80286においては24ビットの物理アドレス空間へのアクセス、そして1セグメントあたり最大64キロバイトの空間を提供していた。これまでに述べた機能を使ってセグメントレジスタを意識したセグメント方式の仮想記憶を使ったOS環境を作成することが可能になった。しかし286リリース当時、プロテクトモードはすぐに広く使われたわけではない[12]。プロセッサをリセットする以外にリアルモードに戻ることができないため、BIOSまたはDOSコールにアクセスすることができないなどの欠点があり、幅広い採用が妨げられた[13]。幅広い採用が見送られた他の要因として、286 では4本のセグメントレジスタでそれぞれ16ビットのセグメントしかアクセスできなかった。すなわち一度にアクセスできるメモリの範囲は 4*216 バイト(256キロバイト)に限られていた[12]。
286 は 8086 との互換性を保つため、起動時にはリアルモードで動作を開始するようになっていた[4]。リアルモードでは 8086 と全く同じ動作をするので、古いソフトウェアも 286 で修正せずに動作することができる。286 の拡張機能にアクセスするには、オペレーティングシステムがプロセッサをプロテクトモードに移行させる必要がある。それによって24ビット・アドレッシングが可能になり、224 バイト(16メガバイト)のメモリにアクセス可能となる[10]。
286のプロテクトモードではセグメントディスクリプタの「Presentビット」の属性を使用した仮想記憶管理が行われる。OSがセグメントの内容をハードディスクにスワップアウトすると、「Presentビット」を0にする。アプリケーションプログラムがこのセグメントの値をセグメントレジスタにロードすると「セグメント不在例外(Not Present)」INT#11が発生する。OSはハードディスクからセグメントの内容をスワップインし、「Presentビット」を1にする。その後アプリケーションプログラムは実行を再開する。
プロテクトモードではプログラムのバグによってプロテクトモードのルールに反する不正な値をセグメントレジスタにロードした場合「一般保護例外 (General Protetion Fault)」INT#13 が発生するが、OSやCPUは、アプリケーションプログラムが本来ロードしようとしていた正しい値がわからず、再実行ができない状態になる。
80386では、セグメントリミットが4GBのフラットモデルを使用すれば、アプリケーションプログラムがセグメントレジスタのロードを行う必要はなくなる。
1985年[8]にリリースされた 386 では、プロテクトモードの採用を妨げていた様々な問題への対処が行われている[12]。386のアドレスバスは32ビット幅で、232 バイト(4ギガバイト)のメモリにアクセス可能である[14]。セグメントも32ビットに拡大され、複数のセグメントを切り換えることなく4ギガバイトのアドレス空間にアクセス可能となった[14]。アドレスバスとセグメントレジスタの拡大に加えて、セキュリティと安定性を向上させるための様々な機能が追加されている[15]。
プロテクトモードはその後、Microsoft Windows やLinuxなどほぼ全てのx86アーキテクチャ(IA-32)上のオペレーティングシステムで使われるようになった[16]。
386のリリースに際し、以下の機能がプロテクトモードに追加された[2]。
386がリリースされる以前は、プロテクトモードにいったん入った後、リアルモードに戻る直接的方法が提供されていなかった。IBMはその対処として、キーボードコントローラからCPUをリセットする技法を考案した。その際に実行を再開するアドレスをBIOS Data Areaに、リセット後の動作を決めるシャットダウンコードをリアルタイムクロック用の不揮発性CMOSメモリに、それ以外のプログラムの動作継続に必要な情報は当該プログラム自身のデータ領域などに保存するようにした。これによりBIOSがCPUをほぼ同じ状態に復旧することが可能となり、リセット前のコードの実行を続行できる。その後、トリプルフォールトを発生させて286CPUをリセットするという手段がとられた。こちらの方がキーボードコントローラを使うよりも高速できれいだった。
プロテクトモードに入るには、少なくとも3エントリ(ヌルディスクリプタ、コードセグメントディスクリプタ、データセグメントディスクリプタ)を持つGDTを作る必要がある。そして、アドレスバスのA20線(21番目のアドレス線)をイネーブルにし、1メガバイトを越えるメモリにアクセスできるようにする必要がある(電源投入直後は、古いソフトウェアの互換性を保証するため、20番目までのアドレス線しか使わない設定になっている)。この2段階を実行後、CR0レジスタのPEビットをセットして、far jump 命令を実行することで命令プリフェッチキューをクリアしなければならない。
; set PE bit
mov eax, cr0
or eax, 1
mov cr0, eax
; far jump (cs = selector of code segment)
jmp cs:@pm
@pm:
; Now we are in PM.
386以降でプロテクトモードからリアルモードに移行するには、セグメントレジスタをリアルモードの値にし、A20 線をディセーブルし、CR0レジスタのPEビットをクリアすればよく、286で必要だった初期設定が不要になった。
プロテクトモードには、セキュリティとシステム安定性を向上させるべくOSのアプリケーションに対する制御を強化するよう設計された機能がいくつかある[3]。それら追加機能により、適切なハードウェアサポートなしではかなり難しいか不可能であったOS機能の実装が可能になった[18]。
特権は数字が低い程高い特権で0,1,2,3の4段階の特権リングがある。リングの使用により、システムソフトウェアがタスクのデータやコールゲートへのアクセスまたは特権命令実行を制限できるようになった[19]。大抵の環境では、OSと一部のデバイスドライバがリング0で、アプリケーションがリング3で動作する[19]。高い特権のコードへの呼出はゲートディスクリプタや例外を通す必要がある。
Intel 80286 Programmer's Reference Manual には以下のような記述がある[20]。
「 | 80286は 8086および80186のほとんどのアプリケーションプログラムに対して上方互換を保っている。8086のほとんどのアプリケーションプログラムは、80286上で再コンパイルまたは再アセンブルすることでプロテクトモードで実行可能である。 | 」 |
リアルモードのコードとのバイナリ互換性の観点で、アプリケーションプログラマにとって最も明白な変化は、物理メモリが最大16MBまでアクセス可能になった点と1GBの仮想記憶であった[21]。これには制限がなかったわけではなく、以下のような技法を使っていたアプリケーションはプロテクトモードでは動作しなかった[22]。
実際にはほぼ全てのMS-DOSアプリケーションプログラムがこれらのルールのいずれかに反していた[24]。そのため、386では仮想86モードが導入された。そういった潜在的問題はあったが、Windows 3.0 とその後継では Windows 2.x でのリアルモードの各種アプリケーションをプロテクトモードで動作させるのに互換性を利用している[25]。
386のプロテクトモードでは、インテルが仮想8086モード (virtual 8086 mode) と呼ぶものを提供している。仮想86モードは8086向けのコードを修正することなく、プロテクトモードのOS上のタスクの1つとして安全に動作させることができる[26]。ただし完全な後方互換性があるわけではない。セグメント操作や特権命令、ハードウェアへの直接アクセス、自己書き換えコードなどを使っているプログラムの場合、例外が発生するのでOSがそれに対処しなければならない[27]。さらに仮想86モードで動作するアプリケーションが入出力 (I/O) 関連命令を使用するとトラップ処理が行われるので、性能が低下する[28]。そういった制約があるため、8086上のプログラムの一部は仮想86モードでは動作できない。結果としてシステムソフトウェアは、古いソフトウェアを扱う際にセキュリティか互換性のいずれかを犠牲にすることになった。例えば Windows NT では互換性を犠牲にし、行儀の悪いDOSアプリケーションをサポートしなくなった[29]。
リアルモードでは、論理アドレスは2つの16ビットの部分で構成されており、論理アドレスが直接物理的メモリ位置に対応している。論理アドレスのセグメント部は16バイトでセグメントのベースアドレスを指しており、セグメントは物理アドレス 0, 16, 32, ..., 220-16 から始まる。論理アドレスのオフセット部はセグメント内のオフセットであり、物理アドレスは physical_address : = segment_part × 16 + offset
という式で計算できる(アドレスバスのA20線がイネーブル状態の場合[30])。各セグメントのサイズは 216 バイトである。
プロテクトモードでは、セグメント部が16ビットの「セレクタ」で置き換えられ、セレクタの上位13ビット(ビット 3 から ビット15)がディスクリプタテーブル内のエントリのインデックスとなっている。下位2ビット(ビット 0 とビット 1)は要求の特権レベルを定義しており、0から3までの値をとり、0が最も特権が高く、3が最も特権が低い。残るビット(ビット 2)はGDTかLDTの指定に使われる。
ディスクリプタテーブルのエントリには以下の情報が含まれる。
ディスクリプタテーブルのエントリ内のセグメントアドレスは24ビットであり、リアルモードとは異なり任意のアドレスからセグメントを開始できるようになった。リミット値は16ビット幅であり、セグメントの大きさは1バイトから 216 バイトまで指定可能である。これに基づいて計算したリニアアドレスは物理メモリアドレスとなる。
ディスクリプタテーブルのエントリ内のセグメントアドレスは32ビットに拡張されている。リミット値は20ビット幅に拡張され、しかもG-ビットで倍率を指定できる。
ページングを使わない場合、リニアアドレスはそのまま物理メモリアドレスとなるが、ページングを使う場合はリニアアドレスはページング機構への入力となる。
386プロセッサではアドレスオフセットとしても32ビットの値を使用する。
286のプロテクトモードとの互換性を維持するため、D-ビットが追加されている。D-ビットがオフとなっているコードセグメントは16ビット・セグメントと解釈され、その中の命令は16ビット命令として実行される。
AVLビットは未使用でありOSで使用可能である。
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
インテル予約済み | |||||||||||||||
P | DPL | S=1 | E=1 | C | R | A | ベースアドレス[23:16] | ||||||||
ベースアドレス[15:0] | |||||||||||||||
リミット[15:0] |
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ベースアドレス[31:24] | G | D | 0 | AVL | リミット[19:16] | ||||||||||
P | DPL | S=1 | E=1 | C | R | A | ベースアドレス[23:16] | ||||||||
ベースアドレス[15:0] | |||||||||||||||
リミット[15:0] |
386では仮想86モードだけでなく、ページング方式がプロテクトモードに追加された[31]。ページングを使うことでシステムソフトウェアはページと呼ばれるメモリ単位毎にタスクのアクセスを制限し制御することができる。多くのOSでページングを使ってタスク毎の仮想アドレス空間を作成している。それによって、あるタスクが他のタスクのメモリを操作することを防ぐ。また、ページ単位で主記憶装置からより低速だが大容量の補助記憶装置(ハードディスクなど)へ移すこともできる[32]。それにより、主記憶装置の物理メモリ量以上のメモリを使用可能となる[32]。x86アーキテクチャではページの制御をページディレクトリとページテーブルという2段階の配列で行う。1ページは基本的には4キロバイトであり、ページテーブルはプロセッサによって直接読まれるため構造が決まっている。元々、ページディレクトリの大きさは1ページ、すなわち4キロバイトで、1,024個のページ・ディレクトリ・エントリ (PDE) がそこに格納されていた。PDEにはページテーブルへのポインタが含まれている。ページテーブルも本来は4キロバイトの大きさで、1,024個のページ・テーブル・エントリ (PTE) で構成されている。PTEには実際の物理ページのアドレス(ポインタ)が含まれており、その場合の各ページは4キロバイトである。これで1024×1024×4096バイト、すなわち4GBの論理空間を全て指すことが可能になる。
Pentium Pro以降に導入された物理アドレス拡張(PAE)が有効である場合は、一エントリあたりが8バイトとなり、もう一段4つのエントリを持つ32バイトのテーブルが追加される。これにより、64ビットの物理アドレス空間の任意のアドレスを4×512×512×4096バイトの32ビットの空間に配置することが可能になるが、実際の物理アドレス空間はもっと少ない。[33] このページエントリの構造は段数を増やしてAMD64でも継承されている。また、Pentiumからはページサイズ拡張 (PSE) という拡張により、一部のページを一段へらして4メガバイト(PAEの時は2メガバイト)の大きなページとしてマップすることが可能になっている。Pentium II以降においては、PAEでない通常のページテーブルエントリの構造を保ちながら4MBのページに限り36ビットの物理空間にアクセス可能な36ビットPSEとよばれる機能も提供している。
286で導入されたリング、コールゲート、タスク・ステート・セグメント (TSS) により、x86アーキテクチャでプリエンプティブマルチタスクが可能となった。TSSにより、他のタスクに影響を及ぼすことなく汎用レジスタ群、セグメントセレクタフィールド群、スタック群を書き換え可能となった。TSSはまた、タスク毎に特権レベルやI/Oポート許可マップを設定できる。
多くのOSはTSSの全機能を使ってはいない[34]。これは移植性を考慮したためでもあるし、ハードウェアによるタスク切り替えが性能問題を抱えているためでもある[34]。結果として多くのOSはハードウェアとソフトウェアを共に活用してマルチタスクを実現している[35]。