プロテクトモードのセグメント |
さて、今回はプロテクトモードについてのお話ですが、リアルモード時の欠陥を補うための工夫が沢山されているので かなり難しい内容となっていますが、順番に理解していってください。
リアルモードでは、セグメントレジスタにセグメントアドレスをセットするだけでセグメントを利用できるように なりますが、プロテクトモードでは、その前にいくつかの準備をしなくてはなりません。 基本的に3つの段階がありますので、順番に説明していきます。
セグメントレジスタにセットする値のことを、セグメントを選択するという意味で「セレクタ値」と呼びます。 概念的にはセグメントの番号だと思ってください。 リアルモードでは、セグメントアドレスとセグメントベースの関係は固定的でしたが、プロテクトモードでは、セレクタ値と セグメントベースとの間に固定的な関係はありません。つまり、セレクタ値の大小が物理メモリ上での配置とは関係しないということです。 また、セレクタ値に対応するセグメントが(メモリ上に)存在しない場合さえあります。
リアルモードのセグメントアドレスは16ビットの大きさで、セグメントベースの値に応じて0000hからFFFFhまでの範囲の値をとります。 プロテクトモードのセレクタ値も同様に16ビットの大きさを持つのですが、任意の値を利用できるわけではありません。まず、セレクタ値の うち、0はセグメントレジスタを無効にするために使われます。つまり、セグメントレジスタに0をセットすると、そのセグメントレジスタを 使ったセグメントのアクセスはできなくなります。有効な値としては、0008h、0010h、0018h、0020hと、8つおきの値のみを考えることにします。 その他の値は、また後で説明します。
・セグメントディスクリプタプロテクトモードでも、セグメントレジスタにセグメントのセレクタ値をロードすると、その番号の付いたセグメントを指し示す ことになります。CPUがメモリをアクセスするときにはCPU内のアドレス変換回路がセグメントベースを算出しますが、セレクタ値は リニアアドレスと直接対応しないので、単純な演算で算出することはできません。
その対応は、あらかじめ「セグメントディスクリプタ」と呼ばれるものに設定しておく仕組みになっています。
プロテクトモードへの移行486のすぐれた機能をを使用するためにはまず、プロテクトモードへ移行しなければなりません。 リアルモードからプロテクトモードへ移行するのは、意外と簡単でCR0(コントロールレジスタ0)というレジスタのPEビット (プロテクションイネーブル・ビット)のフラグを立てるだけです。
mov eax,cr0 or eax,1 mov cr0,eax ; 以降プロテクトモードで実行される。 ;
さあ、これであなたもプロテクター使いと言いたい所ですが、実際問題プロテクトモードでは保護機能が働くのでその他にも下準備が必要です。 その手順を以下に示します。
GDTの作成 | セグメントの設定 |
GDTRの設定 | |
IDTの作成 | 割り込みの設定 |
IDTRの設定 | |
アドレスバスA20ビットのマスク解除 | アドレス制限の設定 |
CR0のPEビットをセット | プロテクトモードへの移行 |
セグメントレジスタの再設定 |
;----------------------------------------------------------- ; The transition from Real-mode to Protect-mode. ; The transition from Protect-mode to Real-mode. ; 2000/12/06 Daisuke c-maker@spica.freemail.ne.jp ;----------------------------------------------------------- .386p _TEXT segment byte public use16 'CODE' assume cs:_TEXT ;----------------------------------------------------------- ; _RealToProtect ; ; The transition from Real-mode to Protect-mode. ;----------------------------------------------------------- public _RealToProtect _RealToProtect proc near push bp mov bp,sp ; cli ; mov eax,cr0 or eax,1 mov cr0,eax ; jmp flush_q1 flush_q1: pop bp ret _RealToProtect endp ;----------------------------------------------------------- ; _ProtectToReal ; ; The transition from Protect-mode to Real-mode. ;----------------------------------------------------------- public _ProtectToReal _ProtectToReal proc near push bp mov bp,sp ; mov eax,cr0 or eax,0fffffffeh mov cr0,eax ; jmp flush_q2 flush_q2: pop bp ret _ProtectToReal endp _TEXT ends end ;-----------------------------------------------------------プロテクトモードに移行した直後にまず実行しなければならないことは、jmp命令です。486はパイプライン処理を行っている ため、命令の取り出し・解釈・実行を並列して行っています。つまり、プロテクトモードへ移行した次の瞬間に関しては、 リアルモードとして読み込んだ命令が、プロテクトモードとして実行されるという不都合が起きてしまいます。
時間↓ | 命令読み出し | 命令解釈 | 命令実行 | |||
1↓ | 命令→ | mov cr0,eax | 命令→ | or eax,1 | 命令→ | mov eax,cr0 |
2↓ | 次の命令 | mov cr0,eax | or eax,1 | |||
3↓ | 次の次の命令 | 次の命令 | mov cr0,eax |
時間3の瞬間にプロテクトモードへ移行するわけだが、パイプライン処理によって、すでに次の命令と 次の次の命令の命令が読み込まれているので、時間4で次の命令を 実行しようとした瞬間に、リアルモードで読み込み、解釈した命令をプロテクトモードで実行しようとし不都合が発生する。 JMP命令は、実行されると、一度パイプライン中の命令が無効化されるので、この機能を利用しパイプラインを初期化しています。
リアルモードへの移行リアルモードへの移行は、プロテクトモードへの移行の仕方とまったく一緒です。(上記プログラム参照)
上記のプログラムはC言語からコールできるようにしたあるので、実際に動作させてみましょう。
//----------------------------------------------------------- // The transition from Real-mode to Protect-mode, and return. // 2000/12/06 Daisuke c-maker@spica.freemail.ne.jp //----------------------------------------------------------- #include < stdio.h > #include < dos.h > #include "proto0.h" void main() { printf("Now going to Potected mode..."); getchar(); RealToProtect(); ProtectToReal(); printf("Returned from Protected mode.\n"); } //----------------------------------------------------------- //----------------------------------------------------------- // Header of transition to Real-mode or Protect-mode. // 2000/12/06 Daisuke c-maker@spica.freemail.ne.jp //----------------------------------------------------------- void RealToProtect(); void ProtectToReal(); //-----------------------------------------------------------(2000/12/06)