ビット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を介してカーネルをロードします。
これはもちろん、 小さな小さなスタブカーネルを作成することも意味します。