備忘録やめた

備忘録として使用していたけどやめた.このブログに載せてあるコードのライセンスは別途記載がない限りWTFPL OR NYSLです.

はりぼてOSの仮GDT解説

要約

GDTの各フィールドと,はりぼてOSのasmhead.nas内で仮設定するGDTのヌルセレクタ以外の2つのエントリの各ビットがどのような意味を持つのかを解説します.

背景

RamenOSにページングを実装するために,カーネルIDT,GDTの位置を動かしていましたが,GDTの設定も変える必要があることに気づきました.GDTの各ビットの意味はあまり気にしていなかったので,一度纏めておきたいと思い記事を書きました.

GDTのエントリ構成

この本のP.55で述べられているエントリの構造を表にしています.括弧内はフィールドに割り当てられているビット数です.

Limitの15-0ビット(16)
Base Addressの15-0ビット(16)
P(1)DPL(2)S(1)Type(4)Base Addressの23-16ビット(8)
Base Addressの31-24ビット(8)G(1)D/B(1)L(1)AVL(1)Limitの19-16ビット(4)

フィールド解説

解説はIntel® 64 and IA-32 Architectures Software Developer’s ManualsのVolume 3,3.4.5を基にしています.

Limit
セグメントの大きさを指定します.後述するTypeフィールドのExpansion-directionも確認してください.
Base Address
物理メモリ上でのセグメントの開始番地です.
P
セグメントがメモリ上に存在するかを示します.このフィールドが0だとセグメントがメモリ上にないことを示し,1だとセグメントがメモリ上にあることを示します.
DPL
セグメントの特権レベルを指定します.0が最強の権限で,基本的にはOSのカーネルが持ちます.3はアプリケーションの権限です.1と2はOSのサービスの権限ですが,サービスがない場合,0と3の値のみ使用します.
S
セグメントの種類を指定します.このフィールドが0だとセグメントはシステムセグメントとなり,1だとコードセグメントあるいはデータセグメントとなります.
Type
セグメントの種類を指定します(後述).
G
Limit値の解釈を指定します.このフィールドが0の場合,実際のセグメントの大きさは(limit値)Byteとなり,1の場合,実際のセグメントの大きさは(limit値)×4KBとなります.
D/B
既定のオペラントまたはスタックポインタのサイズ,あるいはスタックの上限を指定します(後述).
L
IA-32eの場合,このフィールドはセグメントが64ビットコードを格納しているかを示します.0の場合,互換モードで実行し,1の場合,このセグメント内の命令は64ビットモードで実行されます.非IA-32eやコードセグメントでない場合,このビットは常に0にするべきです.
AVL
このフィールドはシステムソフトウェアが自由に使用して構いません.
Typeフィールド

Sフィールドに設定されているビットによってTypeフィールドの値の解釈が変わります.

共通

Sフィールドの値に関わらず常に同じ意味を持ちます.

最下位ビット:Accessed
セグメントにアクセスされるとこのビットが1になります.
Sフィールドが0の場合

セグメントはシステムセグメントです.TSS等が該当します.ここでは割愛します.

Sフィールドが1の場合

セグメントはデータセグメントあるいはコードセグメントです.Typeフィールドの最上位ビットが0の場合,セグメントはデータセグメントとなり,1の場合,セグメントはコードセグメントとなります.

Typeフィールドの最上位ビットが0の場合
第1ビット:Write-enable
このビットが0の場合,セグメントは読み取り専用となります.1の場合,セグメントへの書き込みが可能となります.
第2ビット:Expansion-direction
このビットが0の場合,セグメントの論理アドレスの範囲は0からlimit値までとなります.1の場合,セグメントの論理アドレスの範囲は(limit値 + 1)から0xFFFF(D/Bフィールドの値が0の場合)または0xFFFFFFFF(D/Bフィールドの値が1の場合)となります.
Typeフィールドの最上位ビットが1の場合
第1ビット:Read enable
このビットが0の場合,セグメントの実行のみが可能です.1の場合,実行に加えて読み取りが可能になります.
第2ビット:Conforming
このビットが0の,且つ特権レベルの異なるセグメントへの実行の遷移を行おうとすると,一般保護例外が発生します.このビットが1の,且つ特権レベルの異なるセグメントへの実行の遷移を行っても,それまでの特権レベルを維持したまま実行を継続することが出来ます.
D/Bフィールド

セグメントの種類によって何を意味するのかが変わります.

実行可能コードセグメントの場合

セグメント内の命令で参照される実効アドレスやオペラントの既定の長さを指定します.このビットが0の場合,既定では16ビットアドレス,16ビットまたは8ビットのオペラントと見做されます.1の場合,既定では32ビットアドレス,32ビットまたは8ビットのオペラントと見做されます.

スタックセグメントの場合*1

スタックポインタのサイズを指定します.このビットが0の場合,16ビットのスタックポインタが使用され,1の場合,32ビットのスタックポインタが使用されます.

Expansion-directionなデータセグメントの場合

セグメントの上限を指定します.このビットが0の場合,上限は0xFFFFで,1の場合,上限は0xFFFFFFFFです.

他のセグメントの場合

このビットは常に1にしなければなりません.

asmhead.nasで仮に設定するGDTのエントリ確認

はりぼてOSでは保護モードに切り替える際,仮のGDTを設定します.最初のヌルセレクタ以外の2つのエントリを確認します.

        DW      0xFFFF,0x0000,0x9200,0x00CF ; 読み書き可能セグメント32bit
        DW      0xFFFF,0x0000,0x9A28,0x0047 ; 実行可能セグメント32bit(bootpack用)

1番目のエントリ

項目
Limit 0xFFFFF
Base Address 0
P 1
DPL 0
S 1
Type 0b0010
G 1
D/B 1
L 0
AVL 0

Gフィールドの値が1のため,実際のセグメントの大きさは(0xFFFFF + 1) × 4KB = 4GBとなります.すなわちこのセグメントの領域はメモリの全範囲です.

現在セグメントはメモリ上に存在するので,Pフィールドの値は1です.

セグメントはアプリケーション用ではないため,DPLフィールドの値は0です.

Sフィールドが1で且つTypeフィールドが0b0010のため,このセグメントは読み書き可能なデータセグメントで,セグメントの論理アドレスの範囲は0から0xFFFFFまでとなります.そのためD/Bフィールドは1,Lフィールドは0にする必要があります.

2番目のエントリ

項目
Limit 0x7FFFF
Base Address 0x00280000
P 1
DPL 0
S 1
Type 0b1010
G 0
D/B 1
L 0
AVL 0

Gフィールドの値が0のため,実際のセグメントの大きさは(0x7FFFF + 1)KB = 512KBです.これはbootpack.hrbの,想定される最大の大きさと一致しています.

現在セグメントはメモリ上に存在するので,Pフィールドの値は1です.

セグメントはOS用なので,DPLフィールドの値は0です.

Sフィールドが1で且つ,Typeフィールドが0b1010のため,このセグメントは実行と読み取りが可能なコードセグメントです.はりぼてOSでは32ビット保護モードを使用するため,D/Bフィールドは1にする必要があります.はりぼてOSは64ビットモードを使用しないため,Lフィールドは0にします.

Base Addressはasmhead.nas内でBOTPAKとしてだったり,Cファイル内でも定数として定義されています.

BOTPAK EQU 0x00280000

*1:SSレジスタが指しているデータセグメント