Operating Systems Development Series
Protected Mode
by Mike, 2008, 2009

はじめに

ようこそ!:)

このシリーズでは、これまでに多くのことを学びました。ブートローダ、システムアーキテクチャ、ファイルシステム、そしてリアルモードアドレッシングを深く見てきました。しかし、32 ビットの世界についてはまだ見ていません。そして、私たちは 32bit OS を構築していないのでしょうか?

このチュートリアルでは、32ビットの世界へようこそ!ということで、32ビットの世界へのジャンプをします。もちろん、まだ16ビットの世界は終わっていませんが、プロテクトモードに入るのはずっと簡単でしょう。

では、さっそく始めましょうこのチュートリアルは以下の内容をカバーしています。

  • プロテクトモード理論
  • プロテクトモードアドレッシング
  • プロテクトモードへの移行
  • グローバルディスクリプターテーブル(GDT)
準備はいいですか?

stdio.inc

よりオブジェクト指向に近づけるために、すべての入出力ルーチンを stdio.inc ファイルに移動しました。くれぐれも、C言語の標準ライブラリであるstdio.libと一緒にしないでください。両者にはほとんど共通点がありません。標準ライブラリについては、カーネルと並行して作業を開始する予定です。

とりあえず...このファイルです。

;************************************************* ; stdio.inc ; -Input/Output routines ; ; OS Development Series ;************************************************* %ifndef __STDIO_INC_67343546FDCC56AAB872_INCLUDED__ %define __STDIO_INC_67343546FDCC56AAB872_INCLUDED__ ;************************************************; ; Puts16 () ; -Prints a null terminated string ; DS=>SI: 0 terminated string ;************************************************; bits 16 Puts16: pusha ; save registers .Loop1: lodsb ; load next byte from string from SI to AL or al, al ; Does AL=0? jz Puts16Done ; Yep, null terminator found-bail out mov ah, 0eh ; Nope-Print the character int 10h ; invoke BIOS jmp .Loop1 ; Repeat until null terminator found Puts16Done: popa ; restore registers ret ; we are done, so return %endif ;__STDIO_INC_67343546FDCC56AAB872_INCLUDED__
知らない人のために説明すると、*.INCファイルはIncludeファイルです。puts16関数の説明は省略します--ブートローダで使ったのと全く同じルーチンですが、pusha/popaが追加されているだけです。

ステージ2へようこそ

ブートローダは小さいです。ブートローダは小さいので、有用なことは何もできません。ブートローダは 512 バイトに制限されていることを思い出してください。それ以上でも以下でもありません。ステージ2をロードするコードは、すでにほぼ512バイトでした。これでは小さすぎるのです。

このため、ブートローダには別のプログラムをロードさせるだけでよいのです。FAT12ファイルシステムのおかげで、2番目のプログラムはほとんどどんなセクタ数でも可能です。このため、512バイトの制限はありません。 これは私たちにとって素晴らしいことです。これが読者の皆さん、ステージ2です。

Stage 2ブートローダは、カーネルのためにすべてをセットアップします。これはWindowsでいうところのNTLDR(NT Loader)と同じようなものです。実際、私はこのプログラムをKRNLDR(Kernel Loader)と名付けています。Stage 2はカーネルのロードを担当するので、KRNLDR.SYSとしました。

KRNLDR -- Stage 2ブートローダは、いくつかのことを行います。それは、次のようなことです。

  • プロテクトモードを有効にし、プロテクトモードへ移行する
  • BIOS情報を取得する
  • カーネルをロードして実行する
  • ブートオプションの提供(例えば、セーフモード)
  • 設定ファイルにより、KRNLDRを複数のOSカーネルからブートさせることができます。
  • 最大4GBのメモリにアクセスするための20番目のアドレスラインを有効にする。
  • 基本的な割り込み処理
...そしてもっと。実際、Stage 2のローダーはCとx86のアセンブリが混在していることがよくあります。

想像できるように、ステージ2ブートローダを書くこと自体が大きなプロジェクトになりえます。しかし、すでに動作しているカーネルがなければ、高度なブートローダを開発することはほぼ不可能です。このため、私たちは重要な細部 (上に太字で示した部分) についてだけ心配するつもりです。動くカーネルを手に入れたら、ブートローダに戻ってくるかもしれません。

私たちはまず、保護されたモードに入ることに注目します。皆さんの多くは 32 ビットの世界に行きたくてうずうずしていることでしょう。

プロテクトモードの世界

やったー!ついにその時が来たのです!私が「プロテクトモード」と言うのをよく耳にすると思いますが、以前にも少し詳しく説明しました。ご存知のように、プロテクトモードはメモリを保護するためのものです。80x86プロセッサは、GDT(Global Descriptor Table)に基づいてメモリ領域をマッピングしています。GDTに従わない場合、プロセッサはGPF(General Protection Fault)例外を生成します。割込みハンドラを設定していないため、トリプルフォルトになります。

もう少し詳しく見てみましょう。

ディスクリプタ・テーブル

ディスクリプタテーブルとは、何かを定義したり、マップしたりするものです。 ディスクリプタテーブルには、3つの種類があります。グローバル記述子テーブル(GDT)、ローカル記述子テーブル(LDT)、割り込み記述子テーブル(IDT)で、それぞれのベースアドレスはGDTR、LDTR、IDTRのx86プロセッサレジスタに格納されています。これらは特殊なレジスタを使用するため、特殊な命令を必要とします。注:これらの命令のいくつかは、Ring 0カーネルレベルプログラムに特有のものです。一般のRing 3プログラムがこれらを使用しようとすると、GPF(General Protection Fault)例外が発生します。今回の場合は、まだ割り込みの処理をしていないので、トリプルフォールトが発生します。

グローバルディスクリプターテーブル

これは私たちにとって重要で、ブートローダとカーネルの両方で見ることができます。

グローバル記述子テーブル(GDT)はグローバルメモリマップを定義しています。どのメモリを実行できるのか(コードディスクリプタ)、どの領域にデータがあるのか(データディスクリプタ)を定義しています。

ディスクリプタはプロパティを定義する--つまり、何かを記述するものであることを覚えておいてください。GDTの場合、開始アドレスとベースアドレス、セグメントリミット、そして仮想メモリまで記述します。このことは、実際に使ってみるとよくわかると思いますが、心配しないでください。

GDTは通常、Nullディスクリプタ(すべてのゼロを含む)、コードディスクリプタデータディスクリプタの3つのディスクリプタを持っています。

なるほど......では、「ディスクリプタ」とは何でしょうか?GDTでは、"Descriptor "は8バイトのQWORD値で、そのディスクリプタのプロパティを記述しています。その形式は

  • Bits 56-63: Bits 24-32 of the base address
  • Bit 55: Granularity
    • 0: None
    • 1: Limit gets multiplied by 4K
  • Bit 54: Segment type
    • 0: 16 bit
    • 1: 32 bit
  • Bit 53: Reserved-Should be zero
  • Bits 52: Reserved for OS use
  • Bits 48-51: Bits 16-19 of the segment limit
  • Bit 47 Segment is in memory (Used with Virtual Memory)
  • Bits 45-46: Descriptor Privilege Level
    • 0: (Ring 0) Highest
    • 3: (Ring 3) Lowest
  • Bit 44: Descriptor Bit
    • 0: System Descriptor
    • 1: Code or Data Descriptor
  • Bits 41-43: Descriptor Type
    • Bit 43: Executable segment
      • 0: Data Segment
      • 1: Code Segment
    • Bit 42: Expansion direction (Data segments), conforming (Code Segments)
    • Bit 41: Readable and Writable
      • 0: Read only (Data Segments); Execute only (Code Segments)
      • 1: Read and write (Data Segments); Read and Execute (Code Segments)
  • Bit 40: Access bit (Used with Virtual Memory)
  • Bits 16-39: Bits 0-23 of the Base Address
  • Bits 0-15: Bits 0-15 of the Segment Limit
...かなり不細工でしょう?基本的に、ビットパターンを構築することで、8バイトのビットパターンがディスクリプタの様々なプロパティを記述することになります。各ディスクリプタは、そのメモリセグメントに対するプロパティを定義する。

簡単に説明するために、メモリの最初のバイトからバイト0xFFFFFFまで、読み取りと書き込みの権限を持つコードとデータディスクリプタを定義するテーブルを構築してみます。これはちょうど、メモリ内の任意の場所を読み書きできることを意味します。

まず、GDTについて見ていきます。

; This is the beginning of the GDT. Because of this, its offset is 0. ; null descriptor dd 0 ; null descriptor--just fill 8 bytes with zero dd 0 ; Notice that each descriptor is exactally 8 bytes in size. THIS IS IMPORTANT. ; Because of this, the code descriptor has offset 0x8. ; code descriptor: ; code descriptor. Right after null descriptor dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; granularity db 0 ; base high ; Because each descriptor is 8 bytes in size, the Data descritpor is at offset 0x10 from ; the beginning of the GDT, or 16 (decimal) bytes from start. ; data descriptor: ; data descriptor dw 0FFFFh ; limit low (Same as code) dw 0 ; base low db 0 ; base middle db 10010010b ; access db 11001111b ; granularity db 0 ; base high
以上です。悪名高きGDTです。このGDTには、3つのディスクリプタが含まれています--それぞれ8バイトのサイズです。NULLディスクリプタ、コードディスクリプタ、データディスクリプタです。各ディスクリプタの各ビットは、上のビット表(コードの上に表示)に直接対応しています。

それぞれをビットに分解して、何が起こっているのか見てみましょう。NULLディスクリプタはすべて0なので、他の2つに注目します。

コードセレクタを分解する

もう一度見てみましょう。

; code descriptor: ; code descriptor. Right after null descriptor dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; granularity db 0 ; base high
アセンブリ言語では、宣言されたバイト、word, dword, qword,命令などは、文字通り、互いに直列に並ぶことを覚えておいてください。上の例では、0xffffはもちろん1で埋め尽くされた2バイトです。 これを2進数に分解するのは簡単です。なぜなら、そのほとんどがすでに終わっているからです。
11111111 11111111 00000000 00000000 00000000 10011010 11001111 00000000
上のビットテーブルから)ビット0-15(最初の2バイト)はセグメントの制限を表していることを思い出してください。 これは、セグメント内で0xffff(最初の2バイト)より大きいアドレスは使えないということです。 そうすると、GPFが発生します。

16-39ビット(次の3バイト)はベースアドレス(セグメントの開始アドレス)の0-23ビットを表します。 この場合、0x0です。ベースアドレスが0x0、リミットアドレスが0xFFFFなので、コードセレクタは0x0から0xFFFFまでのすべてのバイトにアクセスすることができるのです。どうです?

次のバイト(バイト6)は、面白いことが起こるところです。それでは、文字どおり、1ビットずつ分解してみましょう。

db 10011010b ; access
  • ビット0 (GDTのビット40):アクセスビット(仮想メモリで使用)。仮想メモリは使わないので(まだ)無視します。したがって、0です
  • ビット1(GDTのビット41):読み取り/書き込み可能なビット です。このビットは(コードセレクタのために)設定されているので、セグメント(0x0から0xFFFFまで)のデータをコードとして読み取り、実行することができます。
  • ビット2(GDTのビット42):「拡張方向」ビットです。これについては、後で詳しく見ていきます。今のところ、無視してください。
  • ビット3(GDTのビット43):プロセッサに、これがコードまたはデータ記述子であることを伝えます。(セットされているので、コードディスクリプタです)
  • ビット4 (GDTのビット44):システム "または "コード/データ "ディスクリプタとして表現します。これはコードセレクタなので、ビットに1がセットされています。
  • ビット5〜6(GDTのビット45〜46):特権レベル(すなわち、リング0またはリング3)である。リング0にいるので、両ビットは0です。
  • ビット7(GDTのビット47)。セグメントがメモリ内にあることを示すために使用されます(仮想メモリで使用されます)。まだ仮想メモリを使用していないので、今は0に設定
  • アクセスバイトは非常に重要です!Ring 3アプリケーションやソフトウェアを実行するために、異なる記述子を定義する必要があります。

    これをまとめると、このバイトは

    次のようになります。

    このバイトは、読み取りと書き込みが可能なセグメントであり、我々はRing 0のコードディスクリプタであることを示します。

    次のバイトを見てみましょう。

    db 11001111b ; granularity db 0 ; base high
  • ビット0~3(GDTでは48~51ビット) セグメントリミットの16-19ビットに相当します。つまり...1111bは0xfに等しい。 このディスクリプタの最初の2バイトでは、最初の15バイトとして0xffffを設定したことを思い出してください。ロービットとハイビットをグループ化すると、0xFFFFFまでアクセスできることになります。どうです?もっといいことが...20番目のアドレス行を有効にすると、このディスクリプタを使って最大4GBのメモリにアクセスできます。この点については、後で詳しく説明します。
  • ビット4(GDTではビット52) :OSが使用するために予約されています。0にセットされている。
  • ビット5 (GDTではビット53) :何かのために予約されています。将来のオプションでしょうか?どうでしょう。0にセットされる。
  • ビット6(GDTのビット54):セグメントタイプ(16ビットまたは32ビット)です。32ビットOSを作るのだから、32ビットにしたいですよね。というわけで、1 にセットします。
  • ビット7(GDTではビット55)。粒度(Granularity)。1にすると、各セグメントは4KBで区切られます。

    最後のバイトはベース(スタート)アドレスの24~32ビットで、もちろん0です。

    データ記述子

    それでは、先ほど作ったGDTに戻って、コードとデータのセレクタを比較してみてください。 1つのビットを除いて、両者はまったく同じです。43ビットです。上記を振り返ってみると、その理由がわかると思います。コードセレクタの場合はセットされ、データセレクタの場合はセットされないのです。

    結論

    これは私が今まで見た(書いた)中で最も包括的なGDTの説明です。

    しかし、それをロードして使用するのは非常に簡単です。実は、ポインタのアドレスを読み込むだけなんです。

    このGDTポインタには、GDTのサイズ(マイナス1!)とGDTの開始アドレスが格納されています。 例えば

    toc: dw end_of_gdt - gdt_data - 1 ; limit (Size of GDT) dd gdt_data ; base of GDT
    gdt_dataはGDTの先頭、end_of_gdtはもちろんGDTの末尾のラベルです。 このポインタのサイズと、その形式に注意してください。GDTポインターはこのフォーマットに従わなければなりません。 そうしないと、予測できない結果、つまりトリプルフォールトが発生します。

    プロセッサは特別なレジスタ、GDTRを使用して、ベースGDTポインタ内のデータを格納します。 GDTRレジスタにGDTをロードするには、特別な命令が必要になります。

    LGDT(Load GDT) を使うのはとても簡単です:

    lgdt [toc] ; load GDT into GDTR
    これは冗談ではなく、本当に簡単なことです。OS Devでは、このような素敵なブレイクを得ることはあまりありません。今のうちに覚えておきましょう。

    Local Descriptor Table

    Local Descriptor Table (LDT) は特殊な用途のために定義されたGDTの小さな形式です。 システムのメモリマップ全体ではなく、最大8191のメモリセグメントだけを定義します。 プロテクテッドモードとは関係ないので、これについては後で詳しく説明します

    Interrupt Descriptor Table

    Interrupt Descriptor Table (IDT) はInterrupt Vector Table (IVT) を定義しています。最初の32個のベクターは、プロセッサが生成するハードウェア例外用に予約されています。例えば、一般保護フォールトやダブルフォールト例外などです。 他の割り込みベクタは、マザーボード上のプログラマブル割り込みコントローラチップを介してマッピングされます。 プロテクトモードの間、このチップを直接プログラムする必要があります。

    PMode Memory Addressing

    PMode(Protected Mode)はリアルモードとは異なるアドレス体系を使用することを忘れないでください リアルモードではセグメント:オフセットというメモリモデルを使いますが、PModeではディスクリプタ:オフセットというモデルを使います。 つまり、PModeでメモリにアクセスするには、GDT内の正しいディスクリプタを経由しなければなりません。 ディスクリプタはCSに保存 されています。例えば、 あるメモリ位置から読み出す場合、どのディスクリプタを使用するかを記述する必要はなく、現在CSにあるディスクリプタを使用 します。

    mov bx, byte [0x1000]

    これは 素晴らしいことですが、特定のディスクリプタを参照する必要がある場合もあります。例えば、リアルモードはGDTを使用しませんが、PModeはGDTを必要とします。このため、プロテクトモードに入るとき、プロテクトモードで実行を継続するために、どのディスクリプタを使用するかを選択する必要があります。結局、Real ModeはGDTが何であるかを知らないので、CSに正しいディスクリプタが含まれる保証はなく、設定する必要があります。 これを行うには、 ディスクリプタを直接設定する必要があります:

    jmp 0x8:Stage2

    このコードは再び見ることになります。最初の数字がディスクリプタであることを思い出してください(PModeはディスクリプタ:アドレスのメモリモデルを使っていることを思い出してください) 。上のGDTを振り返ってみてください。各ディスクリプタは8バイトのサイズであることを思い出してください。この メモリモデルを理解することは、プロテクトモードの仕組みを理解 する上で非常に重要です。

    プロテクトモードへの移行

    プロテクトモードへの移行は非常に簡単です。プロテクトモードに入るには、メモリにアクセスする際の許可レベルを記述した新しい GDT をロードする必要があります。そして、実際にプロセッサをプロテクトモードに切り替えて、32ビットの世界に飛び込む必要があります。簡単そうだと思いませんか? 問題はその詳細です。 ちょっとしたミスがCPUをトリプルフォールトにする可能性があります。

    ステップ1:Global Descriptor Tableをロード

    GDTはメモリへのアクセス方法を記述していることを思い出してください。 GDTを設定しないと、デフォルトのGDTが使用されます(これはBIOSによって設定されます - ROM BIOSではありません)。ご想像の通り、これは決してBIOSの標準的なものではありません。また、GDTの制限に注意しないと(つまり、コードセレクタをデータとしてアクセスすると)、プロセッサはGeneral Protection Fault(GPF)を生成します。 割り込みハンドラが設定されていないため、プロセッサは2番目の障害例外も生成し、トリプルフォルトにつながります。 とにかく...基本的に必要なのはテーブルを作成することです。例:

    ; Offset 0 in GDT: Descriptor code=0 gdt_data: dd 0 ; null descriptor dd 0 ; Offset 0x8 bytes from start of GDT: Descriptor code therfore is 8 ; gdt code: ; code descriptor dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; granularity db 0 ; base high ; Offset 16 bytes (0x10) from start of GDT. Descriptor code therfore is 0x10. ; gdt data: ; data descriptor dw 0FFFFh ; limit low (Same as code) dw 0 ; base low db 0 ; base middle db 10010010b ; access db 11001111b ; granularity db 0 ; base high ;...Other descriptors begin at offset 0x18. Remember that each descriptor is 8 bytes in size? ; Add other descriptors for Ring 3 applications, stack, whatever here... end_of_gdt: toc: dw end_of_gdt - gdt_data - 1 ; limit (Size of GDT) dd gdt_data ; base of GDT
    とりあえず、これでOKです。tocに注目。これはテーブルへのポインタです。ポインタの最初の単語はGDTのサイズ-1です。2番目の単語はGDTの実際のアドレスです。このポインタは、この形式でなければなりません。 1を引くことを忘れないでください!

    GDT(このポインタに基づく)をGDTRレジスタにロードするには、特別なリング0専用命令であるLGDTを使用します。

    cli ; make sure to clear interrupts first! lgdt [toc] ; load GDT into GDTR sti

    シンプル 簡単でしょう?さて...プロテクトモードへ!Gdt

    ;************************************************* ; Gdt.inc ; -GDT Routines ; ; OS Development Series ;************************************************* %ifndef __GDT_INC_67343546FDCC56AAB872_INCLUDED__ %define __GDT_INC_67343546FDCC56AAB872_INCLUDED__ bits 16 ;******************************************* ; InstallGDT() ; - Install our GDT ;******************************************* InstallGDT: cli ; clear interrupts pusha ; save registers lgdt [toc] ; load GDT into GDTR sti ; enable interrupts popa ; restore registers ret ; All done! ;******************************************* ; Global Descriptor Table (GDT) ;******************************************* gdt_data: dd 0 ; null descriptor dd 0 ; gdt code: ; code descriptor dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; granularity db 0 ; base high ; gdt data: ; data descriptor dw 0FFFFh ; limit low (Same as code) dw 0 ; base low db 0 ; base middle db 10010010b ; access db 11001111b ; granularity db 0 ; base high end_of_gdt: toc: dw end_of_gdt - gdt_data - 1 ; limit (Size of GDT) dd gdt_data ; base of GDT %endif ;__GDT_INC_67343546FDCC56AAB872_INCLUDED__

    Step 2: Entering Protected Mode

    CR0レジスタのビットテーブル覚えて ますか? そう ・・・

    • ビット 0 (PE) :システムをプロテクト・ モードに移行する
    • ビット 1 (MP) :Monitor Coprocessor Flag WAIT命令の動作を制御します。
    • ビット 2 (EM) : エミュレートフラグ。設定されると、コプロセッサー命令は例外を生成 する
    • ビット 3 (TS) :Task Switched Flagプロセッサが他のタスクに切り替わったときにセットされます。
    • ビット 4(ET):ExtensionTypeフラグ。これはどのようなタイプのコプロセッサが搭載されているかを教えてくれます。
      • 0 - 80287が搭載
      • 1 - 80387が搭載
    • ビット 5 : 未使用です。
    • ビット 6(PG):メモリページングを有効にします。

    重要なビットはビット0です。ビット0を設定することにより、プロセッサは32ビット状態で実行を継続します。 つまり、ビット0を設定することで、プロテクトモードが有効になります。

    例えば

    mov eax, cr0 ; set bit 0 in CR0-go to pmode or eax, 1 mov cr0, eax

    ビット0がセットされていれば、プロテクトモード(PMode)であることを認識します。 覚えておいてください。

    ビット32を指定するまで、コードは16ビットのままです。コードが16ビットである限り、セグメント:オフセット・メモリ・モデルを使用することができます。

    警告!32ビットコードに入る前に、割り込みが無効であることを確認してください!もし、割り込みが有効になっていると、プロセッサはトリプルフォルトを起こします。(pmodeからIVTにアクセスできないことを思い出してください)

    プロテクトモードに入った後、すぐに問題にぶつかります。リアルモードでは、セグメント:オフセットメモリモデルを使用したのを覚えていますか?しかし、Protected Modeでは、Descriptor:Addressのメモリモデルに依存します。

    また、Real ModeではGDTが何であるかを知りませんが、PModeではアドレッシングモードのため、GDTの使用は必須であることを思い出してください。

    このため、リアルモードでは、CSには、使用するディスクリプタではなく、最後に使用したセグメントアドレスが格納されます。 PModeはCSを使用して現在のコードディスクリプタを保存していることを思い出してください。従って、CSを修正する(コードディスクリプタに設定する)には、コードディスクリプタを使用して、ファージャンプする必要があります。

    コードディスクリプタは0x8(GDTの先頭から8バイトのオフセット)なので、以下のようにジャンプします。

    jmp 08h:Stage3 ; far jump to fix CS. Remember that the code selector is 0x8!

    また、PModeに入ると、すべてのセグメントを(不正確なため)正しいディスクリプタ番号にリセットしなければなりません。

    mov ax, 0x10 ; set data segments to data selector (0x10) mov ds, ax mov ss, ax mov es, ax

    データディスクリプタがGDTの先頭から16 (0x10) バイトだったことを思い出してください。 なぜGDT内部の参照(ディスクリプタの選択)がすべてオフセットなのか不思議かもしれません。 何のオフセット なのか? LGDT命令でロードしたGDTポインターを覚えていますか?プロセッサは、GDTポインタを指すように設定したベースアドレスから、すべてのオフセットアドレスを基に します。

    以下は、ステージ2ブートローダの全体です:
    bits 16 ; Remember the memory map-- 0x500 through 0x7bff is unused above the BIOS data area. ; We are loaded at 0x500 (0x50:0) org 0x500 jmp main ; go to start ;******************************************************* ; Preprocessor directives ;******************************************************* %include "stdio.inc" ; basic i/o routines %include "Gdt.inc" ; Gdt routines ;******************************************************* ; Data Section ;******************************************************* LoadingMsg db "Preparing to load operating system...", 0x0D, 0x0A, 0x00 ;******************************************************* ; STAGE 2 ENTRY POINT ; ; -Store BIOS information ; -Load Kernel ; -Install GDT; go into protected mode (pmode) ; -Jump to Stage 3 ;******************************************************* main: ;-------------------------------; ; Setup segments and stack ; ;-------------------------------; cli ; clear interrupts xor ax, ax ; null segments mov ds, ax mov es, ax mov ax, 0x9000 ; stack begins at 0x9000-0xffff mov ss, ax mov sp, 0xFFFF sti ; enable interrupts ;-------------------------------; ; Print loading message ; ;-------------------------------; mov si, LoadingMsg call Puts16 ;-------------------------------; ; Install our GDT ; ;-------------------------------; call InstallGDT ; install our GDT ;-------------------------------; ; Go into pmode ; ;-------------------------------; cli ; clear interrupts mov eax, cr0 ; set bit 0 in cr0--enter pmode or eax, 1 mov cr0, eax jmp 08h:Stage3 ; far jump to fix CS. Remember that the code selector is 0x8! ; Note: Do NOT re-enable interrupts! Doing so will triple fault! ; We will fix this in Stage 3. ;****************************************************** ; ENTRY POINT FOR STAGE 3 ;****************************************************** bits 32 ; Welcome to the 32 bit world! Stage3: ;-------------------------------; ; Set registers ; ;-------------------------------; mov ax, 0x10 ; set data segments to data selector (0x10) mov ds, ax mov ss, ax mov es, ax mov esp, 90000h ; stack begins from 90000h ;******************************************************* ; Stop execution ;******************************************************* STOP: cli hlt

    まとめ

    私は興奮していますがあなたはそうでしょうか?このチュートリアルでは、多くのことを学びました。GDT、ディスクリプタ・テーブル、そしてプロテクト・モードへの移行。

    32ビットの世界へようこそ

    これは私たちにとって素晴らしいことです。ほとんどのコンパイラは32ビットコードしか生成しないので、プロテクトモードが必要です。 これで、Cやアセンブリなど、ほとんどすべての言語で書かれた32ビットプログラムを実行できるようになります。 しかし、まだ16ビットの 世界が終わったわけではありません。 次のチュートリアルでは、BIOSの情報を取得し、FAT12を介してカーネルをロードします。 これはもちろん、 小さな小さなスタブカーネルを作成することも意味します。