[PR]100万円が無料で当たる!:今すぐ応募して現金を当てよう!

文字出力の実装が終わりましたので、 32bitモードに移行することにしましょう。
32bitモードへの移行は次の流れで行います。

  1. 割り込みを禁止します(CLI命令)。
  2. NMIを禁止します。
  3. A20マスクを解除します。マスタブートローダの段階では、 「A20マスク」、つまり メモリアドレスのビット20を強制的に 0 とする機能が有効になっています。 つまり、address &= 0xffefffff; という演算が行われます。 このままでは1Mを超えるメモリを有効に利用できませんので、 A20マスクの解除が必要になります。
  4. LGDTによりGDTの位置をCPUに教えます。
  5. CR0のbit 0に1を設定し、プロテクトモードに移行します。
  6. far jmpによりCSに32bitセレクタを設定します。
  7. DS、ES、FS、GS、SS、ESPに適切な値を設定します。
  8. LIDTによりIDTの位置をCPUに教えます。
  9. NMIを許可します。
  10. 割り込みを許可します(STI命令)。

前半は16bitモード、後半は32bitモードで実行されます。
CR0の値を設定しただけでは16bitモードのままです。
far jmpによりCSを更新することで32bitモードに切り替わります。

まず、NMIの禁止は次のようにします。


I/Oポート70hのbit 7に0を設定すればNMIは無効になります。
しかし、I/Oポート70hに書き込んだ場合、 I/Oポート71hからデータを読み込むことと決められています。
データを読み捨てているのはそのためです。
途中のI/Oポート80hはBIOSの診断コードの読み書きポートですが、 データそのものには興味がありません。
ISAバスは1マイクロ秒のアクセス時間がかかるため、 ウェイトポートとして使っています。

A20マスクの解除は次のようにします。


INT 15h/AX=2401hはA20マスクを解除するBIOSコールです。
このコールで解除できない場合には キーボードコントローラか I/O ポート 92h で マスクを解除する必要があります。
ここでは キーボードコントローラを使って解除しています。
なお boot_kbc_wait は次のとおりです。


次に、GDTの位置をCPUに教え、32bitモードに切り替えます。
GDT用のdescriptorは次の8バイトの構造体です。

オフセットサイズ機能
+00h2-byteリミットのbit 15-0です。
+02h2-byteベースアドレスのbit 15-0です。
+04h1-byteベースアドレスのbit 23-16です。
+05h1-bytedescriptorの種類とフラグです。
bit 7(80h)を設定すると、このdescriptorが存在することを示します。 クリアするとアクセス時に例外が発生します。
bit 5-6(00h,20h,40h,60h)はdescriptorの特権レベル(DPL)を表します。
bit 4-0はdescriptorの種類を表します。特に、 bit 4(10h)はdescriptorがコードやデータのとき設定され、 システム用のときクリアされます。
bit 4-0 の例を示します。
02h:LDT(別の構造体を使用)09h:32-bit TSS(別の構造体を使用)0Bh:32-bit TSS, busy (別の構造体を使用)0Eh:32-bit 割り込みゲート(別の構造体を使用)0Fh:32-bit トラップゲート(別の構造体を使用)10h:読み込み可能なデータ12h:読み込み・書き込み可能なデータ18h:実行可能コード1Ah:実行・読み込み可能なコード
+06h1-byte 下位4bitはリミットのbit 19-16です。
上位4bitはフラグです。
bit 7(80h)を設定すると、リミットが4096倍されます。
bit 6(40h)を設定すると、コードやスタックのデフォルトサイズが32bitになります。
+07h1-byteベースアドレスのbit 31-24です。

まずはdescriptorの配列とそのポインタをメモリ上に準備します。
32bitで表現可能なアドレスすべてにアクセスできる descriptorにはいろいろな種類がありますが、 セレクタのみを使うのであれば、 descriptorは以下の配列で十分です。
セレクタ08hがコード用、セレクタ10hがデータ用です。


CPUのマニュアルには スタックにはexpand-downを使うといった記述がありますが、 特にexpand-downを使う必要はありません。
GDTへのポインタは以下のとおりです。
リニアアドレスの計算に(0060h<<4)を使っているのは カーネルイメージが0060hに読み込まれているためです。 テーブルが他の場所にある場合は適宜置き換えてください。


これらを用いて32bitに切り替えるコードを 書くと、次のようになります。

CR0のbit 0に1を設定してプロテクトモードに移行した後、 csには0008hを、他のセレクタには0010hを設定し、 また esp に暫定スタックとして 110000h を設定しています。
これにより、コード、データ、スタックを 32bitプロテクトモードに移行できました。
エントリポイントのオフセットが16bitの範囲に 収まらない場合は、 db 0EAhの前にopsize prefix(db 66h)を入れて、 オフセットを32bitで与えるとjmpできます。


2005-09-04: descriptorの説明を追加 (最終更新)
2005-08-27: ChangeLog作成
Copyright (C) 2005 hoverkid, all rights reserved.
[PR]衝撃!あなたの本当の裏の顔!:実は貴方はΟΔ県出身?ここで分かる真実